mirror of
https://github.com/netbirdio/netbird.git
synced 2026-04-30 14:16:38 +00:00
Compare commits
73 Commits
feature/in
...
v0.11.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fcf7786a85 | ||
|
|
a78fd69f80 | ||
|
|
4bd5029e7b | ||
|
|
f604956246 | ||
|
|
53c532bbb4 | ||
|
|
8b0a1bbae0 | ||
|
|
e965d6c022 | ||
|
|
11f8249eed | ||
|
|
d63a9ce4a7 | ||
|
|
9cb66bdb5d | ||
|
|
c8ace8bbbe | ||
|
|
509d23c7cf | ||
|
|
1db4027bea | ||
|
|
d4dbc322be | ||
|
|
e19d5dca7f | ||
|
|
157137e4ad | ||
|
|
7d7e576775 | ||
|
|
f37b43a542 | ||
|
|
7e262572a4 | ||
|
|
a768a0aa8a | ||
|
|
ed7ac81027 | ||
|
|
1f845f466c | ||
|
|
270f0e4ce8 | ||
|
|
d0c6d88971 | ||
|
|
4321b71984 | ||
|
|
e8d82c1bd3 | ||
|
|
6aa7a2c5e1 | ||
|
|
2e0bf61e9a | ||
|
|
126af9dffc | ||
|
|
4cdf2df660 | ||
|
|
9a4c9aa286 | ||
|
|
5ed61700ff | ||
|
|
84117a9fb7 | ||
|
|
92b612eba4 | ||
|
|
aeeaa21eed | ||
|
|
d228cd0cb1 | ||
|
|
b41f36fccd | ||
|
|
d2cde4a040 | ||
|
|
84879a356b | ||
|
|
ed2214f9a9 | ||
|
|
4388dcc20b | ||
|
|
4f1f0df7d2 | ||
|
|
08ddf04c5f | ||
|
|
b5ee2174a8 | ||
|
|
7218a3d563 | ||
|
|
04e4407ea7 | ||
|
|
06055af361 | ||
|
|
abd1230a69 | ||
|
|
f7de12daf8 | ||
|
|
c49fb0c40c | ||
|
|
6e9a162877 | ||
|
|
b4e03f4616 | ||
|
|
369a7ef345 | ||
|
|
c88e6a7342 | ||
|
|
2cd9b11e7d | ||
|
|
93d20e370b | ||
|
|
878ca6db22 | ||
|
|
2033650908 | ||
|
|
34c1c7d901 | ||
|
|
051fd3a4d7 | ||
|
|
af69a48745 | ||
|
|
68ff97ba84 | ||
|
|
c5705803a5 | ||
|
|
7e1ae448e0 | ||
|
|
518a2561a2 | ||
|
|
c75ffd0f4b | ||
|
|
e4ad6174ca | ||
|
|
6de313070a | ||
|
|
cd7d1a80c9 | ||
|
|
be7d829858 | ||
|
|
ed1872560f | ||
|
|
de898899a4 | ||
|
|
b63ec71aed |
7
.github/workflows/golang-test-darwin.yml
vendored
7
.github/workflows/golang-test-darwin.yml
vendored
@@ -1,5 +1,10 @@
|
|||||||
name: Test Code Darwin
|
name: Test Code Darwin
|
||||||
on: [push,pull_request]
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
pull_request:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
|
|||||||
59
.github/workflows/golang-test-linux.yml
vendored
59
.github/workflows/golang-test-linux.yml
vendored
@@ -1,5 +1,10 @@
|
|||||||
name: Test Code Linux
|
name: Test Code Linux
|
||||||
on: [push,pull_request]
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
pull_request:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
@@ -33,3 +38,55 @@ jobs:
|
|||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
run: GOARCH=${{ matrix.arch }} go test -exec 'sudo --preserve-env=CI' -timeout 5m -p 1 ./...
|
run: GOARCH=${{ matrix.arch }} go test -exec 'sudo --preserve-env=CI' -timeout 5m -p 1 ./...
|
||||||
|
|
||||||
|
test_client_on_docker:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Install Go
|
||||||
|
uses: actions/setup-go@v2
|
||||||
|
with:
|
||||||
|
go-version: 1.18.x
|
||||||
|
|
||||||
|
|
||||||
|
- name: Cache Go modules
|
||||||
|
uses: actions/cache@v2
|
||||||
|
with:
|
||||||
|
path: ~/go/pkg/mod
|
||||||
|
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-go-
|
||||||
|
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: sudo apt update && sudo apt install -y -q libgtk-3-dev libappindicator3-dev libayatana-appindicator3-dev libgl1-mesa-dev xorg-dev
|
||||||
|
|
||||||
|
- name: Install modules
|
||||||
|
run: go mod tidy
|
||||||
|
|
||||||
|
- name: Generate Iface Test bin
|
||||||
|
run: go test -c -o iface-testing.bin ./iface/...
|
||||||
|
|
||||||
|
- name: Generate RouteManager Test bin
|
||||||
|
run: go test -c -o routemanager-testing.bin ./client/internal/routemanager/...
|
||||||
|
|
||||||
|
- name: Generate Engine Test bin
|
||||||
|
run: go test -c -o engine-testing.bin ./client/internal/*.go
|
||||||
|
|
||||||
|
- name: Generate Peer Test bin
|
||||||
|
run: go test -c -o peer-testing.bin ./client/internal/peer/...
|
||||||
|
|
||||||
|
- run: chmod +x *testing.bin
|
||||||
|
|
||||||
|
- name: Run Iface tests in docker
|
||||||
|
run: docker run -t --cap-add=NET_ADMIN --privileged --rm -v $PWD:/ci -w /ci/iface --entrypoint /busybox/sh gcr.io/distroless/base:debug -c /ci/iface-testing.bin -test.timeout 5m -test.parallel 1
|
||||||
|
|
||||||
|
- name: Run RouteManager tests in docker
|
||||||
|
run: docker run -t --cap-add=NET_ADMIN --privileged --rm -v $PWD:/ci -w /ci/client/internal/routemanager --entrypoint /busybox/sh gcr.io/distroless/base:debug -c /ci/routemanager-testing.bin -test.timeout 5m -test.parallel 1
|
||||||
|
|
||||||
|
- name: Run Engine tests in docker
|
||||||
|
run: docker run -t --cap-add=NET_ADMIN --privileged --rm -v $PWD:/ci -w /ci/client/internal --entrypoint /busybox/sh gcr.io/distroless/base:debug -c /ci/engine-testing.bin -test.timeout 5m -test.parallel 1
|
||||||
|
|
||||||
|
- name: Run Peer tests in docker
|
||||||
|
run: docker run -t --cap-add=NET_ADMIN --privileged --rm -v $PWD:/ci -w /ci/client/internal/peer --entrypoint /busybox/sh gcr.io/distroless/base:debug -c /ci/peer-testing.bin -test.timeout 5m -test.parallel 1
|
||||||
8
.github/workflows/golang-test-windows.yml
vendored
8
.github/workflows/golang-test-windows.yml
vendored
@@ -1,5 +1,10 @@
|
|||||||
name: Test Code Windows
|
name: Test Code Windows
|
||||||
on: [push,pull_request]
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
pull_request:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
pre:
|
pre:
|
||||||
@@ -20,7 +25,6 @@ jobs:
|
|||||||
needs: pre
|
needs: pre
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
|||||||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -9,7 +9,7 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
|
|
||||||
env:
|
env:
|
||||||
SIGN_PIPE_VER: "v0.0.3"
|
SIGN_PIPE_VER: "v0.0.4"
|
||||||
GORELEASER_VER: "v1.6.3"
|
GORELEASER_VER: "v1.6.3"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|||||||
12
.github/workflows/test-docker-compose-linux.yml
vendored
12
.github/workflows/test-docker-compose-linux.yml
vendored
@@ -1,5 +1,10 @@
|
|||||||
name: Test Docker Compose Linux
|
name: Test Docker Compose Linux
|
||||||
on: [push,pull_request]
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
pull_request:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
@@ -51,13 +56,16 @@ jobs:
|
|||||||
CI_NETBIRD_AUTH_JWT_CERTS: https://example.eu.auth0.com/.well-known/jwks.json
|
CI_NETBIRD_AUTH_JWT_CERTS: https://example.eu.auth0.com/.well-known/jwks.json
|
||||||
CI_NETBIRD_AUTH_TOKEN_ENDPOINT: https://example.eu.auth0.com/oauth/token
|
CI_NETBIRD_AUTH_TOKEN_ENDPOINT: https://example.eu.auth0.com/oauth/token
|
||||||
CI_NETBIRD_AUTH_DEVICE_AUTH_ENDPOINT: https://example.eu.auth0.com/oauth/device/code
|
CI_NETBIRD_AUTH_DEVICE_AUTH_ENDPOINT: https://example.eu.auth0.com/oauth/device/code
|
||||||
|
CI_NETBIRD_AUTH_REDIRECT_URI: "/peers"
|
||||||
run: |
|
run: |
|
||||||
grep AUTH_CLIENT_ID docker-compose.yml | grep $CI_NETBIRD_AUTH_CLIENT_ID
|
grep AUTH_CLIENT_ID docker-compose.yml | grep $CI_NETBIRD_AUTH_CLIENT_ID
|
||||||
grep AUTH_AUTHORITY docker-compose.yml | grep $CI_NETBIRD_AUTH_AUTHORITY
|
grep AUTH_AUTHORITY docker-compose.yml | grep $CI_NETBIRD_AUTH_AUTHORITY
|
||||||
grep AUTH_AUDIENCE docker-compose.yml | grep $CI_NETBIRD_AUTH_AUDIENCE
|
grep AUTH_AUDIENCE docker-compose.yml | grep $CI_NETBIRD_AUTH_AUDIENCE
|
||||||
grep AUTH_SUPPORTED_SCOPES docker-compose.yml | grep "$CI_NETBIRD_AUTH_SUPPORTED_SCOPES"
|
grep AUTH_SUPPORTED_SCOPES docker-compose.yml | grep "$CI_NETBIRD_AUTH_SUPPORTED_SCOPES"
|
||||||
grep USE_AUTH0 docker-compose.yml | grep $CI_NETBIRD_USE_AUTH0
|
grep USE_AUTH0 docker-compose.yml | grep $CI_NETBIRD_USE_AUTH0
|
||||||
grep NETBIRD_MGMT_API_ENDPOINT docker-compose.yml | grep "http://localhost:33073"
|
grep NETBIRD_MGMT_API_ENDPOINT docker-compose.yml | grep "http://localhost:33073"
|
||||||
|
grep AUTH_REDIRECT_URI docker-compose.yml | grep $CI_NETBIRD_AUTH_REDIRECT_URI
|
||||||
|
grep AUTH_SILENT_REDIRECT_URI docker-compose.yml | egrep 'AUTH_SILENT_REDIRECT_URI=$'
|
||||||
|
|
||||||
- name: run docker compose up
|
- name: run docker compose up
|
||||||
working-directory: infrastructure_files
|
working-directory: infrastructure_files
|
||||||
|
|||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -10,4 +10,5 @@ infrastructure_files/management.json
|
|||||||
infrastructure_files/docker-compose.yml
|
infrastructure_files/docker-compose.yml
|
||||||
*.syso
|
*.syso
|
||||||
client/.distfiles/
|
client/.distfiles/
|
||||||
infrastructure_files/setup.env
|
infrastructure_files/setup.env
|
||||||
|
.vscode
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ builds:
|
|||||||
- arm64
|
- arm64
|
||||||
- arm
|
- arm
|
||||||
ldflags:
|
ldflags:
|
||||||
- -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.CommitDate}} -X main.builtBy=goreleaser
|
- -s -w -X github.com/netbirdio/netbird/client/system.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.CommitDate}} -X main.builtBy=goreleaser
|
||||||
mod_timestamp: '{{ .CommitTimestamp }}'
|
mod_timestamp: '{{ .CommitTimestamp }}'
|
||||||
|
|
||||||
- id: netbird-signal
|
- id: netbird-signal
|
||||||
|
|||||||
48
README.md
48
README.md
@@ -1,6 +1,6 @@
|
|||||||
<p align="center">
|
<p align="center">
|
||||||
<strong>:hatching_chick: New release! NetBird Easy SSH</strong>.
|
<strong>:hatching_chick: New Release! User Invites.</strong>
|
||||||
<a href="https://github.com/netbirdio/netbird/releases/tag/v0.8.0">
|
<a href="https://github.com/netbirdio/netbird/releases">
|
||||||
Learn more
|
Learn more
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
<a href="https://www.codacy.com/gh/netbirdio/netbird/dashboard?utm_source=github.com&utm_medium=referral&utm_content=netbirdio/netbird&utm_campaign=Badge_Grade"><img src="https://app.codacy.com/project/badge/Grade/e3013d046aec44cdb7462c8673b00976"/></a>
|
<a href="https://www.codacy.com/gh/netbirdio/netbird/dashboard?utm_source=github.com&utm_medium=referral&utm_content=netbirdio/netbird&utm_campaign=Badge_Grade"><img src="https://app.codacy.com/project/badge/Grade/e3013d046aec44cdb7462c8673b00976"/></a>
|
||||||
<br>
|
<br>
|
||||||
<a href="https://join.slack.com/t/netbirdio/shared_invite/zt-vrahf41g-ik1v7fV8du6t0RwxSrJ96A">
|
<a href="https://join.slack.com/t/netbirdio/shared_invite/zt-vrahf41g-ik1v7fV8du6t0RwxSrJ96A">
|
||||||
<img src="https://img.shields.io/badge/slack-@wiretrustee-red.svg?logo=slack"/>
|
<img src="https://img.shields.io/badge/slack-@netbird-red.svg?logo=slack"/>
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -40,32 +40,30 @@
|
|||||||
|
|
||||||
It requires zero configuration effort leaving behind the hassle of opening ports, complex firewall rules, VPN gateways, and so forth.
|
It requires zero configuration effort leaving behind the hassle of opening ports, complex firewall rules, VPN gateways, and so forth.
|
||||||
|
|
||||||
NetBird creates an overlay peer-to-peer network connecting machines automatically regardless of their location (home, office, datacenter, container, cloud or edge environments) unifying virtual private network management experience.
|
NetBird uses [NAT traversal techniques](https://en.wikipedia.org/wiki/Interactive_Connectivity_Establishment) to automatically create an overlay peer-to-peer network connecting machines regardless of location (home, office, data center, container, cloud, or edge environments), unifying virtual private network management experience.
|
||||||
|
|
||||||
**Key features:**
|
**Key features:**
|
||||||
- \[x] Automatic IP allocation and network management with a Web UI ([separate repo](https://github.com/netbirdio/dashboard))
|
- \[x] Automatic IP allocation and network management with a Web UI ([separate repo](https://github.com/netbirdio/dashboard))
|
||||||
- \[x] Automatic WireGuard peer (machine) discovery and configuration.
|
- \[x] Automatic WireGuard peer (machine) discovery and configuration.
|
||||||
- \[x] Encrypted peer-to-peer connections without a central VPN gateway.
|
- \[x] Encrypted peer-to-peer connections without a central VPN gateway.
|
||||||
- \[x] Connection relay fallback in case a peer-to-peer connection is not possible.
|
- \[x] Connection relay fallback in case a peer-to-peer connection is not possible.
|
||||||
- \[x] Desktop client applications for Linux, MacOS, and Windows (systray).
|
- \[x] Desktop client applications for Linux, MacOS, and Windows (systray).
|
||||||
- \[x] Multiuser support - sharing network between multiple users.
|
- \[x] Multiuser support - sharing network between multiple users.
|
||||||
- \[x] SSO and MFA support.
|
- \[x] SSO and MFA support.
|
||||||
- \[x] Multicloud and hybrid-cloud support.
|
- \[x] Multicloud and hybrid-cloud support.
|
||||||
- \[x] Kernel WireGuard usage when possible.
|
- \[x] Kernel WireGuard usage when possible.
|
||||||
- \[x] Access Controls - groups & rules.
|
- \[x] Access Controls - groups & rules.
|
||||||
- \[x] Remote SSH access without managing SSH keys.
|
- \[x] Remote SSH access without managing SSH keys.
|
||||||
|
- \[x] Network Routes.
|
||||||
|
|
||||||
**Coming soon:**
|
**Coming soon:**
|
||||||
- \[ ] Network Routes.
|
|
||||||
- \[ ] Private DNS.
|
- \[ ] Private DNS.
|
||||||
- \[ ] Mobile clients.
|
- \[ ] Mobile clients.
|
||||||
- \[ ] Network Activity Monitoring.
|
- \[ ] Network Activity Monitoring.
|
||||||
|
|
||||||
### Secure peer-to-peer VPN with SSO and MFA in minutes
|
### Secure peer-to-peer VPN with SSO and MFA in minutes
|
||||||
<p float="left" align="middle">
|
|
||||||
<img src="docs/media/peerA.gif" width="400"/>
|
https://user-images.githubusercontent.com/700848/197345890-2e2cded5-7b7a-436f-a444-94e80dd24f46.mov
|
||||||
<img src="docs/media/peerB.gif" width="400"/>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
**Note**: The `main` branch may be in an *unstable or even broken state* during development.
|
**Note**: The `main` branch may be in an *unstable or even broken state* during development.
|
||||||
For stable versions, see [releases](https://github.com/netbirdio/netbird/releases).
|
For stable versions, see [releases](https://github.com/netbirdio/netbird/releases).
|
||||||
@@ -101,9 +99,15 @@ See a complete [architecture overview](https://netbird.io/docs/overview/architec
|
|||||||
### Community projects
|
### Community projects
|
||||||
- [NetBird on OpenWRT](https://github.com/messense/openwrt-netbird)
|
- [NetBird on OpenWRT](https://github.com/messense/openwrt-netbird)
|
||||||
|
|
||||||
|
### Support acknowledgement
|
||||||
|
|
||||||
|
In November 2022, NetBird joined the [StartUpSecure program](https://www.forschung-it-sicherheit-kommunikationssysteme.de/foerderung/bekanntmachungen/startup-secure) sponsored by The Federal Ministry of Education and Research of The Federal Republic of Germany. Together with [CISPA Helmholtz Center for Information Security](https://cispa.de/en) NetBird brings the security best practices and simplicity to private networking.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
### Testimonials
|
### Testimonials
|
||||||
We use open-source technologies like [WireGuard®](https://www.wireguard.com/), [Pion ICE (WebRTC)](https://github.com/pion/ice), and [Coturn](https://github.com/coturn/coturn). We very much appreciate the work these guys are doing and we'd greatly appreciate if you could support them in any way (e.g. giving a star or a contribution).
|
We use open-source technologies like [WireGuard®](https://www.wireguard.com/), [Pion ICE (WebRTC)](https://github.com/pion/ice), and [Coturn](https://github.com/coturn/coturn). We very much appreciate the work these guys are doing and we'd greatly appreciate if you could support them in any way (e.g. giving a star or a contribution).
|
||||||
|
|
||||||
### Legal
|
### Legal
|
||||||
[WireGuard](https://wireguard.com/) is a registered trademark of Jason A. Donenfeld.
|
_WireGuard_ and the _WireGuard_ logo are [registered trademarks](https://www.wireguard.com/trademark-policy/) of Jason A. Donenfeld.
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ func newSVCConfig() *service.Config {
|
|||||||
Name: name,
|
Name: name,
|
||||||
DisplayName: "Netbird",
|
DisplayName: "Netbird",
|
||||||
Description: "A WireGuard-based mesh network that connects your devices into a single private network.",
|
Description: "A WireGuard-based mesh network that connects your devices into a single private network.",
|
||||||
|
Option: make(service.KeyValue),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@@ -32,8 +33,13 @@ var installCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
if managementURL != "" {
|
if managementURL != "" {
|
||||||
svcConfig.Arguments = append(svcConfig.Arguments, "--management-url")
|
svcConfig.Arguments = append(svcConfig.Arguments, "--management-url", managementURL)
|
||||||
svcConfig.Arguments = append(svcConfig.Arguments, managementURL)
|
}
|
||||||
|
|
||||||
|
if logFile != "console" {
|
||||||
|
svcConfig.Arguments = append(svcConfig.Arguments, "--log-file", logFile)
|
||||||
|
svcConfig.Option["LogOutput"] = true
|
||||||
|
svcConfig.Option["LogDirectory"] = filepath.Dir(logFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
if runtime.GOOS == "linux" {
|
if runtime.GOOS == "linux" {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"github.com/netbirdio/netbird/util"
|
"github.com/netbirdio/netbird/util"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"google.golang.org/grpc/status"
|
"google.golang.org/grpc/status"
|
||||||
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -18,6 +19,7 @@ import (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
detailFlag bool
|
detailFlag bool
|
||||||
|
ipv4Flag bool
|
||||||
ipsFilter []string
|
ipsFilter []string
|
||||||
statusFilter string
|
statusFilter string
|
||||||
ipsFilterMap map[string]struct{}
|
ipsFilterMap map[string]struct{}
|
||||||
@@ -73,7 +75,7 @@ var statusCmd = &cobra.Command{
|
|||||||
pbFullStatus := resp.GetFullStatus()
|
pbFullStatus := resp.GetFullStatus()
|
||||||
fullStatus := fromProtoFullStatus(pbFullStatus)
|
fullStatus := fromProtoFullStatus(pbFullStatus)
|
||||||
|
|
||||||
cmd.Print(parseFullStatus(fullStatus, detailFlag, daemonStatus, resp.GetDaemonVersion()))
|
cmd.Print(parseFullStatus(fullStatus, detailFlag, daemonStatus, resp.GetDaemonVersion(), ipv4Flag))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
@@ -82,8 +84,9 @@ var statusCmd = &cobra.Command{
|
|||||||
func init() {
|
func init() {
|
||||||
ipsFilterMap = make(map[string]struct{})
|
ipsFilterMap = make(map[string]struct{})
|
||||||
statusCmd.PersistentFlags().BoolVarP(&detailFlag, "detail", "d", false, "display detailed status information")
|
statusCmd.PersistentFlags().BoolVarP(&detailFlag, "detail", "d", false, "display detailed status information")
|
||||||
statusCmd.PersistentFlags().StringSliceVar(&ipsFilter, "filter-by-ips", []string{}, "filters the detailed output by a list of one or more IPs, e.g. --filter-by-ips 100.64.0.100,100.64.0.200")
|
statusCmd.PersistentFlags().BoolVar(&ipv4Flag, "ipv4", false, "display only NetBird IPv4 of this peer, e.g., --ipv4 will output 100.64.0.33")
|
||||||
statusCmd.PersistentFlags().StringVar(&statusFilter, "filter-by-status", "", "filters the detailed output by connection status(connected|disconnected), e.g. --filter-by-status connected")
|
statusCmd.PersistentFlags().StringSliceVar(&ipsFilter, "filter-by-ips", []string{}, "filters the detailed output by a list of one or more IPs, e.g., --filter-by-ips 100.64.0.100,100.64.0.200")
|
||||||
|
statusCmd.PersistentFlags().StringVar(&statusFilter, "filter-by-status", "", "filters the detailed output by connection status(connected|disconnected), e.g., --filter-by-status connected")
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseFilters() error {
|
func parseFilters() error {
|
||||||
@@ -142,7 +145,19 @@ func fromProtoFullStatus(pbFullStatus *proto.FullStatus) nbStatus.FullStatus {
|
|||||||
return fullStatus
|
return fullStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseFullStatus(fullStatus nbStatus.FullStatus, printDetail bool, daemonStatus string, daemonVersion string) string {
|
func parseFullStatus(fullStatus nbStatus.FullStatus, printDetail bool, daemonStatus string, daemonVersion string, flag bool) string {
|
||||||
|
|
||||||
|
interfaceIP := fullStatus.LocalPeerState.IP
|
||||||
|
|
||||||
|
ip, _, err := net.ParseCIDR(interfaceIP)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if ipv4Flag {
|
||||||
|
return fmt.Sprintf("%s\n", ip)
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
managementStatusURL = ""
|
managementStatusURL = ""
|
||||||
signalStatusURL = ""
|
signalStatusURL = ""
|
||||||
@@ -164,8 +179,6 @@ func parseFullStatus(fullStatus nbStatus.FullStatus, printDetail bool, daemonSta
|
|||||||
signalConnString = "Connected"
|
signalConnString = "Connected"
|
||||||
}
|
}
|
||||||
|
|
||||||
interfaceIP := fullStatus.LocalPeerState.IP
|
|
||||||
|
|
||||||
if fullStatus.LocalPeerState.KernelInterface {
|
if fullStatus.LocalPeerState.KernelInterface {
|
||||||
interfaceTypeString = "Kernel"
|
interfaceTypeString = "Kernel"
|
||||||
} else if fullStatus.LocalPeerState.IP == "" {
|
} else if fullStatus.LocalPeerState.IP == "" {
|
||||||
|
|||||||
@@ -62,18 +62,18 @@ func startManagement(t *testing.T, config *mgmt.Config) (*grpc.Server, net.Liste
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
s := grpc.NewServer()
|
s := grpc.NewServer()
|
||||||
store, err := mgmt.NewStore(config.Datadir)
|
store, err := mgmt.NewFileStore(config.Datadir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
peersUpdateManager := mgmt.NewPeersUpdateManager()
|
peersUpdateManager := mgmt.NewPeersUpdateManager()
|
||||||
accountManager, err := mgmt.BuildManager(store, peersUpdateManager, nil)
|
accountManager, err := mgmt.BuildManager(store, peersUpdateManager, nil, "", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
turnManager := mgmt.NewTimeBasedAuthSecretsManager(peersUpdateManager, config.TURNConfig)
|
turnManager := mgmt.NewTimeBasedAuthSecretsManager(peersUpdateManager, config.TURNConfig)
|
||||||
mgmtServer, err := mgmt.NewServer(config, accountManager, peersUpdateManager, turnManager)
|
mgmtServer, err := mgmt.NewServer(config, accountManager, peersUpdateManager, turnManager, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,6 +101,7 @@ done:
|
|||||||
Pop $2
|
Pop $2
|
||||||
Exch $1
|
Exch $1
|
||||||
FunctionEnd
|
FunctionEnd
|
||||||
|
|
||||||
!macro GetAppFromCommand in out
|
!macro GetAppFromCommand in out
|
||||||
Push "${in}"
|
Push "${in}"
|
||||||
Call GetAppFromCommand
|
Call GetAppFromCommand
|
||||||
@@ -117,7 +118,7 @@ Call GetAppFromCommand ; Remove quotes and parameters from UninstCommand
|
|||||||
Pop $0
|
Pop $0
|
||||||
Pop $1
|
Pop $1
|
||||||
GetFullPathName $2 "$0\.."
|
GetFullPathName $2 "$0\.."
|
||||||
ExecWait '"$0" $1 _?=$2'
|
ExecWait '"$0" /S $1 _?=$2'
|
||||||
Delete "$0" ; Extra cleanup because we used _?=
|
Delete "$0" ; Extra cleanup because we used _?=
|
||||||
RMDir "$2"
|
RMDir "$2"
|
||||||
Pop $2
|
Pop $2
|
||||||
@@ -126,30 +127,27 @@ Pop $0
|
|||||||
!macroend
|
!macroend
|
||||||
|
|
||||||
Function .onInit
|
Function .onInit
|
||||||
|
StrCpy $INSTDIR "${INSTALL_DIR}"
|
||||||
ReadRegStr $R0 HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Wiretrustee" "UninstallString"
|
|
||||||
${If} $R0 != ""
|
|
||||||
MessageBox MB_YESNO|MB_ICONQUESTION "Wiretrustee is installed. We must remove it before installing Netbird. Procced?" IDNO noWTUninstOld
|
|
||||||
!insertmacro UninstallPreviousNSIS $R0 "/NoMsgBox"
|
|
||||||
noWTUninstOld:
|
|
||||||
${EndIf}
|
|
||||||
|
|
||||||
ReadRegStr $R0 HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\$(^NAME)" "UninstallString"
|
ReadRegStr $R0 HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\$(^NAME)" "UninstallString"
|
||||||
${If} $R0 != ""
|
${If} $R0 != ""
|
||||||
MessageBox MB_YESNO|MB_ICONQUESTION "$(^NAME) is already installed. Do you want to remove the previous version?" IDNO noUninstOld
|
# if silent install jump to uninstall step
|
||||||
!insertmacro UninstallPreviousNSIS $R0 "/NoMsgBox"
|
IfSilent uninstall
|
||||||
noUninstOld:
|
|
||||||
|
MessageBox MB_YESNO|MB_ICONQUESTION "NetBird is already installed. We must remove it before installing upgrading NetBird. Proceed?" IDNO done IDYES uninstall
|
||||||
|
|
||||||
|
uninstall:
|
||||||
|
!insertmacro UninstallPreviousNSIS $R0 "/NoMsgBox"
|
||||||
|
done:
|
||||||
|
|
||||||
${EndIf}
|
${EndIf}
|
||||||
FunctionEnd
|
FunctionEnd
|
||||||
######################################################################
|
######################################################################
|
||||||
Section -MainProgram
|
Section -MainProgram
|
||||||
${INSTALL_TYPE}
|
${INSTALL_TYPE}
|
||||||
SetOverwrite ifnewer
|
# SetOverwrite ifnewer
|
||||||
SetOutPath "$INSTDIR"
|
SetOutPath "$INSTDIR"
|
||||||
File /r "..\\dist\\netbird_windows_amd64\\"
|
File /r "..\\dist\\netbird_windows_amd64\\"
|
||||||
|
|
||||||
SectionEnd
|
SectionEnd
|
||||||
|
|
||||||
######################################################################
|
######################################################################
|
||||||
|
|
||||||
Section -Icons_Reg
|
Section -Icons_Reg
|
||||||
@@ -172,24 +170,29 @@ SetShellVarContext current
|
|||||||
CreateShortCut "$SMPROGRAMS\${APP_NAME}.lnk" "$INSTDIR\${UI_APP_EXE}"
|
CreateShortCut "$SMPROGRAMS\${APP_NAME}.lnk" "$INSTDIR\${UI_APP_EXE}"
|
||||||
CreateShortCut "$DESKTOP\${APP_NAME}.lnk" "$INSTDIR\${UI_APP_EXE}"
|
CreateShortCut "$DESKTOP\${APP_NAME}.lnk" "$INSTDIR\${UI_APP_EXE}"
|
||||||
SetShellVarContext all
|
SetShellVarContext all
|
||||||
|
SectionEnd
|
||||||
|
|
||||||
|
Section -Post
|
||||||
ExecWait '"$INSTDIR\${MAIN_APP_EXE}" service install'
|
ExecWait '"$INSTDIR\${MAIN_APP_EXE}" service install'
|
||||||
Exec '"$INSTDIR\${MAIN_APP_EXE}" service start'
|
ExecWait '"$INSTDIR\${MAIN_APP_EXE}" service start'
|
||||||
# sleep a bit for visibility
|
# sleep a bit for visibility
|
||||||
Sleep 1000
|
Sleep 1000
|
||||||
SectionEnd
|
SectionEnd
|
||||||
|
|
||||||
######################################################################
|
######################################################################
|
||||||
|
|
||||||
Section Uninstall
|
Section Uninstall
|
||||||
${INSTALL_TYPE}
|
${INSTALL_TYPE}
|
||||||
|
|
||||||
ExecWait '"$INSTDIR\${MAIN_APP_EXE}" service stop'
|
ExecWait '"$INSTDIR\${MAIN_APP_EXE}" service stop'
|
||||||
Exec '"$INSTDIR\${MAIN_APP_EXE}" service uninstall'
|
ExecWait '"$INSTDIR\${MAIN_APP_EXE}" service uninstall'
|
||||||
|
|
||||||
# kill ui client
|
# kill ui client
|
||||||
ExecWait `taskkill /im ${UI_APP_EXE}.exe`
|
ExecWait `taskkill /im ${UI_APP_EXE}.exe`
|
||||||
|
|
||||||
# wait the service uninstall take unblock the executable
|
# wait the service uninstall take unblock the executable
|
||||||
Sleep 3000
|
Sleep 3000
|
||||||
|
Delete "$INSTDIR\${UI_APP_EXE}"
|
||||||
|
Delete "$INSTDIR\${MAIN_APP_EXE}"
|
||||||
RmDir /r "$INSTDIR"
|
RmDir /r "$INSTDIR"
|
||||||
|
|
||||||
SetShellVarContext current
|
SetShellVarContext current
|
||||||
@@ -209,4 +212,4 @@ SetShellVarContext current
|
|||||||
SetOutPath $INSTDIR
|
SetOutPath $INSTDIR
|
||||||
ShellExecAsUser::ShellExecAsUser "" "$DESKTOP\${APP_NAME}.lnk"
|
ShellExecAsUser::ShellExecAsUser "" "$DESKTOP\${APP_NAME}.lnk"
|
||||||
SetShellVarContext all
|
SetShellVarContext all
|
||||||
FunctionEnd
|
FunctionEnd
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ package internal
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/ssh"
|
"github.com/netbirdio/netbird/client/ssh"
|
||||||
"github.com/netbirdio/netbird/iface"
|
"github.com/netbirdio/netbird/iface"
|
||||||
mgm "github.com/netbirdio/netbird/management/client"
|
mgm "github.com/netbirdio/netbird/management/client"
|
||||||
@@ -11,8 +14,6 @@ import (
|
|||||||
"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"
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var managementURLDefault *url.URL
|
var managementURLDefault *url.URL
|
||||||
@@ -32,15 +33,33 @@ func init() {
|
|||||||
// Config Configuration type
|
// Config Configuration type
|
||||||
type Config struct {
|
type Config struct {
|
||||||
// Wireguard private key of local peer
|
// Wireguard private key of local peer
|
||||||
PrivateKey string
|
PrivateKey string
|
||||||
PreSharedKey string
|
PreSharedKey string
|
||||||
ManagementURL *url.URL
|
ManagementURL *url.URL
|
||||||
AdminURL *url.URL
|
AdminURL *url.URL
|
||||||
WgIface string
|
WgIface string
|
||||||
WgPort int
|
WgPort int
|
||||||
IFaceBlackList []string
|
IFaceBlackList []string
|
||||||
|
DisableIPv6Discovery bool
|
||||||
// SSHKey is a private SSH key in a PEM format
|
// SSHKey is a private SSH key in a PEM format
|
||||||
SSHKey string
|
SSHKey string
|
||||||
|
|
||||||
|
// ExternalIP mappings, if different than the host interface IP
|
||||||
|
//
|
||||||
|
// External IP must not be behind a CGNAT and port-forwarding for incoming UDP packets from WgPort on ExternalIP
|
||||||
|
// to WgPort on host interface IP must be present. This can take form of single port-forwarding rule, 1:1 DNAT
|
||||||
|
// mapping ExternalIP to host interface IP, or a NAT DMZ to host interface IP.
|
||||||
|
//
|
||||||
|
// A single mapping will take the form of: external[/internal]
|
||||||
|
// external (required): either the external IP address or "stun" to use STUN to determine the external IP address
|
||||||
|
// internal (optional): either the internal/interface IP address or an interface name
|
||||||
|
//
|
||||||
|
// examples:
|
||||||
|
// "12.34.56.78" => all interfaces IPs will be mapped to external IP of 12.34.56.78
|
||||||
|
// "12.34.56.78/eth0" => IPv4 assigned to interface eth0 will be mapped to external IP of 12.34.56.78
|
||||||
|
// "12.34.56.78/10.1.2.3" => interface IP 10.1.2.3 will be mapped to external IP of 12.34.56.78
|
||||||
|
|
||||||
|
NATExternalIPs []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
|
||||||
@@ -51,11 +70,12 @@ func createNewConfig(managementURL, adminURL, configPath, preSharedKey string) (
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
config := &Config{
|
config := &Config{
|
||||||
SSHKey: string(pem),
|
SSHKey: string(pem),
|
||||||
PrivateKey: wgKey,
|
PrivateKey: wgKey,
|
||||||
WgIface: iface.WgInterfaceDefault,
|
WgIface: iface.WgInterfaceDefault,
|
||||||
WgPort: iface.DefaultWgPort,
|
WgPort: iface.DefaultWgPort,
|
||||||
IFaceBlackList: []string{},
|
IFaceBlackList: []string{},
|
||||||
|
DisableIPv6Discovery: false,
|
||||||
}
|
}
|
||||||
if managementURL != "" {
|
if managementURL != "" {
|
||||||
URL, err := ParseURL("Management URL", managementURL)
|
URL, err := ParseURL("Management URL", managementURL)
|
||||||
@@ -80,7 +100,7 @@ func createNewConfig(managementURL, adminURL, configPath, preSharedKey string) (
|
|||||||
}
|
}
|
||||||
|
|
||||||
config.IFaceBlackList = []string{iface.WgInterfaceDefault, "wt", "utun", "tun0", "zt", "ZeroTier", "utun", "wg", "ts",
|
config.IFaceBlackList = []string{iface.WgInterfaceDefault, "wt", "utun", "tun0", "zt", "ZeroTier", "utun", "wg", "ts",
|
||||||
"Tailscale", "tailscale", "docker", "vet"}
|
"Tailscale", "tailscale", "docker", "veth", "br-"}
|
||||||
|
|
||||||
err = util.WriteJson(configPath, config)
|
err = util.WriteJson(configPath, config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -263,7 +283,7 @@ func GetDeviceAuthorizationFlowInfo(ctx context.Context, config *Config) (Device
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return DeviceAuthorizationFlow{
|
deviceAuthorizationFlow := DeviceAuthorizationFlow{
|
||||||
Provider: protoDeviceAuthorizationFlow.Provider.String(),
|
Provider: protoDeviceAuthorizationFlow.Provider.String(),
|
||||||
|
|
||||||
ProviderConfig: ProviderConfig{
|
ProviderConfig: ProviderConfig{
|
||||||
@@ -274,5 +294,29 @@ func GetDeviceAuthorizationFlowInfo(ctx context.Context, config *Config) (Device
|
|||||||
TokenEndpoint: protoDeviceAuthorizationFlow.GetProviderConfig().GetTokenEndpoint(),
|
TokenEndpoint: protoDeviceAuthorizationFlow.GetProviderConfig().GetTokenEndpoint(),
|
||||||
DeviceAuthEndpoint: protoDeviceAuthorizationFlow.GetProviderConfig().GetDeviceAuthEndpoint(),
|
DeviceAuthEndpoint: protoDeviceAuthorizationFlow.GetProviderConfig().GetDeviceAuthEndpoint(),
|
||||||
},
|
},
|
||||||
}, nil
|
}
|
||||||
|
|
||||||
|
err = isProviderConfigValid(deviceAuthorizationFlow.ProviderConfig)
|
||||||
|
if err != nil {
|
||||||
|
return DeviceAuthorizationFlow{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return deviceAuthorizationFlow, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isProviderConfigValid(config ProviderConfig) error {
|
||||||
|
errorMSGFormat := "invalid provider configuration received from management: %s value is empty. Contact your NetBird administrator"
|
||||||
|
if config.Audience == "" {
|
||||||
|
return fmt.Errorf(errorMSGFormat, "Audience")
|
||||||
|
}
|
||||||
|
if config.ClientID == "" {
|
||||||
|
return fmt.Errorf(errorMSGFormat, "Client ID")
|
||||||
|
}
|
||||||
|
if config.TokenEndpoint == "" {
|
||||||
|
return fmt.Errorf(errorMSGFormat, "Token Endpoint")
|
||||||
|
}
|
||||||
|
if config.DeviceAuthEndpoint == "" {
|
||||||
|
return fmt.Errorf(errorMSGFormat, "Device Auth Endpoint")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,11 +3,12 @@ package internal
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/netbirdio/netbird/client/ssh"
|
|
||||||
nbStatus "github.com/netbirdio/netbird/client/status"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/client/ssh"
|
||||||
|
nbStatus "github.com/netbirdio/netbird/client/status"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/system"
|
"github.com/netbirdio/netbird/client/system"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/iface"
|
"github.com/netbirdio/netbird/iface"
|
||||||
@@ -107,7 +108,7 @@ func RunClient(ctx context.Context, config *Config, statusRecorder *nbStatus.Sta
|
|||||||
localPeerState := nbStatus.LocalPeerState{
|
localPeerState := nbStatus.LocalPeerState{
|
||||||
IP: loginResp.GetPeerConfig().GetAddress(),
|
IP: loginResp.GetPeerConfig().GetAddress(),
|
||||||
PubKey: myPrivateKey.PublicKey().String(),
|
PubKey: myPrivateKey.PublicKey().String(),
|
||||||
KernelInterface: iface.WireguardModExists(),
|
KernelInterface: iface.WireguardModuleIsLoaded(),
|
||||||
}
|
}
|
||||||
|
|
||||||
statusRecorder.UpdateLocalPeerState(localPeerState)
|
statusRecorder.UpdateLocalPeerState(localPeerState)
|
||||||
@@ -184,12 +185,14 @@ func RunClient(ctx context.Context, config *Config, statusRecorder *nbStatus.Sta
|
|||||||
func createEngineConfig(key wgtypes.Key, config *Config, peerConfig *mgmProto.PeerConfig) (*EngineConfig, error) {
|
func createEngineConfig(key wgtypes.Key, config *Config, peerConfig *mgmProto.PeerConfig) (*EngineConfig, error) {
|
||||||
|
|
||||||
engineConf := &EngineConfig{
|
engineConf := &EngineConfig{
|
||||||
WgIfaceName: config.WgIface,
|
WgIfaceName: config.WgIface,
|
||||||
WgAddr: peerConfig.Address,
|
WgAddr: peerConfig.Address,
|
||||||
IFaceBlackList: config.IFaceBlackList,
|
IFaceBlackList: config.IFaceBlackList,
|
||||||
WgPrivateKey: key,
|
DisableIPv6Discovery: config.DisableIPv6Discovery,
|
||||||
WgPort: config.WgPort,
|
WgPrivateKey: key,
|
||||||
SSHKey: []byte(config.SSHKey),
|
WgPort: config.WgPort,
|
||||||
|
SSHKey: []byte(config.SSHKey),
|
||||||
|
NATExternalIPs: config.NATExternalIPs,
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.PreSharedKey != "" {
|
if config.PreSharedKey != "" {
|
||||||
|
|||||||
41
client/internal/dns/dbus_linux.go
Normal file
41
client/internal/dns/dbus_linux.go
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/godbus/dbus/v5"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const dbusDefaultFlag = 0
|
||||||
|
|
||||||
|
func isDbusListenerRunning(dest string, path dbus.ObjectPath) bool {
|
||||||
|
obj, closeConn, err := getDbusObject(dest, path)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
defer closeConn()
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
err = obj.CallWithContext(ctx, "org.freedesktop.DBus.Peer.Ping", 0).Store()
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDbusObject(dest string, path dbus.ObjectPath) (dbus.BusObject, func(), error) {
|
||||||
|
conn, err := dbus.SystemBus()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
obj := conn.Object(dest, path)
|
||||||
|
|
||||||
|
closeFunc := func() {
|
||||||
|
closeErr := conn.Close()
|
||||||
|
if closeErr != nil {
|
||||||
|
log.Warnf("got an error closing dbus connection, err: %s", closeErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return obj, closeFunc, nil
|
||||||
|
}
|
||||||
154
client/internal/dns/file_linux.go
Normal file
154
client/internal/dns/file_linux.go
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
fileGeneratedResolvConfContentHeader = "# Generated by NetBird"
|
||||||
|
fileGeneratedResolvConfSearchBeginContent = "search "
|
||||||
|
fileGeneratedResolvConfContentFormat = fileGeneratedResolvConfContentHeader +
|
||||||
|
"\n# If needed you can restore the original file by copying back %s\n\nnameserver %s\n" +
|
||||||
|
fileGeneratedResolvConfSearchBeginContent + "%s\n"
|
||||||
|
)
|
||||||
|
const (
|
||||||
|
fileDefaultResolvConfBackupLocation = defaultResolvConfPath + ".original.netbird"
|
||||||
|
fileMaxLineCharsLimit = 256
|
||||||
|
fileMaxNumberOfSearchDomains = 6
|
||||||
|
)
|
||||||
|
|
||||||
|
var fileSearchLineBeginCharCount = len(fileGeneratedResolvConfSearchBeginContent)
|
||||||
|
|
||||||
|
type fileConfigurator struct {
|
||||||
|
originalPerms os.FileMode
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFileConfigurator() (hostManager, error) {
|
||||||
|
return &fileConfigurator{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fileConfigurator) applyDNSConfig(config hostDNSConfig) error {
|
||||||
|
backupFileExist := false
|
||||||
|
_, err := os.Stat(fileDefaultResolvConfBackupLocation)
|
||||||
|
if err == nil {
|
||||||
|
backupFileExist = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if !config.routeAll {
|
||||||
|
if backupFileExist {
|
||||||
|
err = f.restore()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to configure DNS for this peer using file manager without a Primary nameserver group. Restoring the original file return err: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fmt.Errorf("unable to configure DNS for this peer using file manager without a Primary nameserver group")
|
||||||
|
}
|
||||||
|
managerType, err := getOSDNSManagerType()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch managerType {
|
||||||
|
case fileManager, netbirdManager:
|
||||||
|
if !backupFileExist {
|
||||||
|
err = f.backup()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to backup the resolv.conf file")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// todo improve this and maybe restart DNS manager from scratch
|
||||||
|
return fmt.Errorf("something happened and file manager is not your prefered host dns configurator, restart the agent")
|
||||||
|
}
|
||||||
|
|
||||||
|
var searchDomains string
|
||||||
|
appendedDomains := 0
|
||||||
|
for _, dConf := range config.domains {
|
||||||
|
if dConf.matchOnly {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if appendedDomains >= fileMaxNumberOfSearchDomains {
|
||||||
|
// lets log all skipped domains
|
||||||
|
log.Infof("already appended %d domains to search list. Skipping append of %s domain", fileMaxNumberOfSearchDomains, dConf.domain)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if fileSearchLineBeginCharCount+len(searchDomains) > fileMaxLineCharsLimit {
|
||||||
|
// lets log all skipped domains
|
||||||
|
log.Infof("search list line is larger than %d characters. Skipping append of %s domain", fileMaxLineCharsLimit, dConf.domain)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
searchDomains += " " + dConf.domain
|
||||||
|
appendedDomains++
|
||||||
|
}
|
||||||
|
content := fmt.Sprintf(fileGeneratedResolvConfContentFormat, fileDefaultResolvConfBackupLocation, config.serverIP, searchDomains)
|
||||||
|
err = writeDNSConfig(content, defaultResolvConfPath, f.originalPerms)
|
||||||
|
if err != nil {
|
||||||
|
err = f.restore()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("attempt to restore default file failed with error: %s", err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Infof("created a NetBird managed %s file with your DNS settings", defaultResolvConfPath)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fileConfigurator) restoreHostDNS() error {
|
||||||
|
return f.restore()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fileConfigurator) backup() error {
|
||||||
|
stats, err := os.Stat(defaultResolvConfPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("got an error while checking stats for %s file. Error: %s", defaultResolvConfPath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
f.originalPerms = stats.Mode()
|
||||||
|
|
||||||
|
err = copyFile(defaultResolvConfPath, fileDefaultResolvConfBackupLocation)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("got error while backing up the %s file. Error: %s", defaultResolvConfPath, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fileConfigurator) restore() error {
|
||||||
|
err := copyFile(fileDefaultResolvConfBackupLocation, defaultResolvConfPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("got error while restoring the %s file from %s. Error: %s", defaultResolvConfPath, fileDefaultResolvConfBackupLocation, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return os.RemoveAll(fileDefaultResolvConfBackupLocation)
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeDNSConfig(content, fileName string, permissions os.FileMode) error {
|
||||||
|
log.Debugf("creating managed file %s", fileName)
|
||||||
|
var buf bytes.Buffer
|
||||||
|
buf.WriteString(content)
|
||||||
|
err := os.WriteFile(fileName, buf.Bytes(), permissions)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("got an creating resolver file %s. Error: %s", fileName, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyFile(src, dest string) error {
|
||||||
|
stats, err := os.Stat(src)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("got an error while checking stats for %s file when copying it. Error: %s", src, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
bytesRead, err := os.ReadFile(src)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("got an error while reading the file %s file for copy. Error: %s", src, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.WriteFile(dest, bytesRead, stats.Mode())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("got an writing the destination file %s for copy. Error: %s", dest, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
79
client/internal/dns/host.go
Normal file
79
client/internal/dns/host.go
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
nbdns "github.com/netbirdio/netbird/dns"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type hostManager interface {
|
||||||
|
applyDNSConfig(config hostDNSConfig) error
|
||||||
|
restoreHostDNS() error
|
||||||
|
}
|
||||||
|
|
||||||
|
type hostDNSConfig struct {
|
||||||
|
domains []domainConfig
|
||||||
|
routeAll bool
|
||||||
|
serverIP string
|
||||||
|
serverPort int
|
||||||
|
}
|
||||||
|
|
||||||
|
type domainConfig struct {
|
||||||
|
domain string
|
||||||
|
matchOnly bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockHostConfigurator struct {
|
||||||
|
applyDNSConfigFunc func(config hostDNSConfig) error
|
||||||
|
restoreHostDNSFunc func() error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockHostConfigurator) applyDNSConfig(config hostDNSConfig) error {
|
||||||
|
if m.applyDNSConfigFunc != nil {
|
||||||
|
return m.applyDNSConfigFunc(config)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("method applyDNSSettings is not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockHostConfigurator) restoreHostDNS() error {
|
||||||
|
if m.restoreHostDNSFunc != nil {
|
||||||
|
return m.restoreHostDNSFunc()
|
||||||
|
}
|
||||||
|
return fmt.Errorf("method restoreHostDNS is not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func newNoopHostMocker() hostManager {
|
||||||
|
return &mockHostConfigurator{
|
||||||
|
applyDNSConfigFunc: func(config hostDNSConfig) error { return nil },
|
||||||
|
restoreHostDNSFunc: func() error { return nil },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func dnsConfigToHostDNSConfig(dnsConfig nbdns.Config, ip string, port int) hostDNSConfig {
|
||||||
|
config := hostDNSConfig{
|
||||||
|
routeAll: false,
|
||||||
|
serverIP: ip,
|
||||||
|
serverPort: port,
|
||||||
|
}
|
||||||
|
for _, nsConfig := range dnsConfig.NameServerGroups {
|
||||||
|
if nsConfig.Primary {
|
||||||
|
config.routeAll = true
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, domain := range nsConfig.Domains {
|
||||||
|
config.domains = append(config.domains, domainConfig{
|
||||||
|
domain: strings.TrimSuffix(domain, "."),
|
||||||
|
matchOnly: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, customZone := range dnsConfig.CustomZones {
|
||||||
|
config.domains = append(config.domains, domainConfig{
|
||||||
|
domain: strings.TrimSuffix(customZone.Domain, "."),
|
||||||
|
matchOnly: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return config
|
||||||
|
}
|
||||||
259
client/internal/dns/host_darwin.go
Normal file
259
client/internal/dns/host_darwin.go
Normal file
@@ -0,0 +1,259 @@
|
|||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"github.com/netbirdio/netbird/iface"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"os/exec"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
netbirdDNSStateKeyFormat = "State:/Network/Service/NetBird-%s/DNS"
|
||||||
|
globalIPv4State = "State:/Network/Global/IPv4"
|
||||||
|
primaryServiceSetupKeyFormat = "Setup:/Network/Service/%s/DNS"
|
||||||
|
keySupplementalMatchDomains = "SupplementalMatchDomains"
|
||||||
|
keySupplementalMatchDomainsNoSearch = "SupplementalMatchDomainsNoSearch"
|
||||||
|
keyServerAddresses = "ServerAddresses"
|
||||||
|
keyServerPort = "ServerPort"
|
||||||
|
arraySymbol = "* "
|
||||||
|
digitSymbol = "# "
|
||||||
|
scutilPath = "/usr/sbin/scutil"
|
||||||
|
searchSuffix = "Search"
|
||||||
|
matchSuffix = "Match"
|
||||||
|
)
|
||||||
|
|
||||||
|
type systemConfigurator struct {
|
||||||
|
// primaryServiceID primary interface in the system. AKA the interface with the default route
|
||||||
|
primaryServiceID string
|
||||||
|
createdKeys map[string]struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newHostManager(_ *iface.WGIface) (hostManager, error) {
|
||||||
|
return &systemConfigurator{
|
||||||
|
createdKeys: make(map[string]struct{}),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *systemConfigurator) applyDNSConfig(config hostDNSConfig) error {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if config.routeAll {
|
||||||
|
err = s.addDNSSetupForAll(config.serverIP, config.serverPort)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else if s.primaryServiceID != "" {
|
||||||
|
err = s.removeKeyFromSystemConfig(getKeyWithInput(primaryServiceSetupKeyFormat, s.primaryServiceID))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.primaryServiceID = ""
|
||||||
|
log.Infof("removed %s:%d as main DNS resolver for this peer", config.serverIP, config.serverPort)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
searchDomains []string
|
||||||
|
matchDomains []string
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, dConf := range config.domains {
|
||||||
|
if dConf.matchOnly {
|
||||||
|
matchDomains = append(matchDomains, dConf.domain)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
searchDomains = append(searchDomains, dConf.domain)
|
||||||
|
}
|
||||||
|
|
||||||
|
matchKey := getKeyWithInput(netbirdDNSStateKeyFormat, matchSuffix)
|
||||||
|
if len(matchDomains) != 0 {
|
||||||
|
err = s.addMatchDomains(matchKey, strings.Join(matchDomains, " "), config.serverIP, config.serverPort)
|
||||||
|
} else {
|
||||||
|
log.Infof("removing match domains from the system")
|
||||||
|
err = s.removeKeyFromSystemConfig(matchKey)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
searchKey := getKeyWithInput(netbirdDNSStateKeyFormat, searchSuffix)
|
||||||
|
if len(searchDomains) != 0 {
|
||||||
|
err = s.addSearchDomains(searchKey, strings.Join(searchDomains, " "), config.serverIP, config.serverPort)
|
||||||
|
} else {
|
||||||
|
log.Infof("removing search domains from the system")
|
||||||
|
err = s.removeKeyFromSystemConfig(searchKey)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *systemConfigurator) restoreHostDNS() error {
|
||||||
|
lines := ""
|
||||||
|
for key := range s.createdKeys {
|
||||||
|
lines += buildRemoveKeyOperation(key)
|
||||||
|
keyType := "search"
|
||||||
|
if strings.Contains(key, matchSuffix) {
|
||||||
|
keyType = "match"
|
||||||
|
}
|
||||||
|
log.Infof("removing %s domains from system", keyType)
|
||||||
|
}
|
||||||
|
if s.primaryServiceID != "" {
|
||||||
|
lines += buildRemoveKeyOperation(getKeyWithInput(primaryServiceSetupKeyFormat, s.primaryServiceID))
|
||||||
|
log.Infof("restoring DNS resolver configuration for system")
|
||||||
|
}
|
||||||
|
_, err := runSystemConfigCommand(wrapCommand(lines))
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("got an error while cleaning the system configuration: %s", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *systemConfigurator) removeKeyFromSystemConfig(key string) error {
|
||||||
|
line := buildRemoveKeyOperation(key)
|
||||||
|
_, err := runSystemConfigCommand(wrapCommand(line))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(s.createdKeys, key)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *systemConfigurator) addSearchDomains(key, domains string, ip string, port int) error {
|
||||||
|
err := s.addDNSState(key, domains, ip, port, true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("added %d search domains to the state. Domain list: %s", len(strings.Split(domains, " ")), domains)
|
||||||
|
|
||||||
|
s.createdKeys[key] = struct{}{}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *systemConfigurator) addMatchDomains(key, domains, dnsServer string, port int) error {
|
||||||
|
err := s.addDNSState(key, domains, dnsServer, port, false)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("added %d match domains to the state. Domain list: %s", len(strings.Split(domains, " ")), domains)
|
||||||
|
|
||||||
|
s.createdKeys[key] = struct{}{}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *systemConfigurator) addDNSState(state, domains, dnsServer string, port int, enableSearch bool) error {
|
||||||
|
noSearch := "1"
|
||||||
|
if enableSearch {
|
||||||
|
noSearch = "0"
|
||||||
|
}
|
||||||
|
lines := buildAddCommandLine(keySupplementalMatchDomains, arraySymbol+domains)
|
||||||
|
lines += buildAddCommandLine(keySupplementalMatchDomainsNoSearch, digitSymbol+noSearch)
|
||||||
|
lines += buildAddCommandLine(keyServerAddresses, arraySymbol+dnsServer)
|
||||||
|
lines += buildAddCommandLine(keyServerPort, digitSymbol+strconv.Itoa(port))
|
||||||
|
|
||||||
|
addDomainCommand := buildCreateStateWithOperation(state, lines)
|
||||||
|
stdinCommands := wrapCommand(addDomainCommand)
|
||||||
|
|
||||||
|
_, err := runSystemConfigCommand(stdinCommands)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("got error while applying state for domains %s, error: %s", domains, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *systemConfigurator) addDNSSetupForAll(dnsServer string, port int) error {
|
||||||
|
primaryServiceKey := s.getPrimaryService()
|
||||||
|
if primaryServiceKey == "" {
|
||||||
|
return fmt.Errorf("couldn't find the primary service key")
|
||||||
|
}
|
||||||
|
|
||||||
|
err := s.addDNSSetup(getKeyWithInput(primaryServiceSetupKeyFormat, primaryServiceKey), dnsServer, port)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Infof("configured %s:%d as main DNS resolver for this peer", dnsServer, port)
|
||||||
|
s.primaryServiceID = primaryServiceKey
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *systemConfigurator) getPrimaryService() string {
|
||||||
|
line := buildCommandLine("show", globalIPv4State, "")
|
||||||
|
stdinCommands := wrapCommand(line)
|
||||||
|
b, err := runSystemConfigCommand(stdinCommands)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("got error while sending the command: ", err)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
scanner := bufio.NewScanner(bytes.NewReader(b))
|
||||||
|
for scanner.Scan() {
|
||||||
|
text := scanner.Text()
|
||||||
|
if strings.Contains(text, "PrimaryService") {
|
||||||
|
return strings.TrimSpace(strings.Split(text, ":")[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *systemConfigurator) addDNSSetup(setupKey, dnsServer string, port int) error {
|
||||||
|
lines := buildAddCommandLine(keySupplementalMatchDomainsNoSearch, digitSymbol+strconv.Itoa(0))
|
||||||
|
lines += buildAddCommandLine(keyServerAddresses, arraySymbol+dnsServer)
|
||||||
|
lines += buildAddCommandLine(keyServerPort, digitSymbol+strconv.Itoa(port))
|
||||||
|
addDomainCommand := buildCreateStateWithOperation(setupKey, lines)
|
||||||
|
stdinCommands := wrapCommand(addDomainCommand)
|
||||||
|
_, err := runSystemConfigCommand(stdinCommands)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("got error while applying dns setup, error: %s", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getKeyWithInput(format, key string) string {
|
||||||
|
return fmt.Sprintf(format, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildAddCommandLine(key, value string) string {
|
||||||
|
return buildCommandLine("d.add", key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildCommandLine(action, key, value string) string {
|
||||||
|
return fmt.Sprintf("%s %s %s\n", action, key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func wrapCommand(commands string) string {
|
||||||
|
return fmt.Sprintf("open\n%s\nquit\n", commands)
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildRemoveKeyOperation(key string) string {
|
||||||
|
return fmt.Sprintf("remove %s\n", key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildCreateStateWithOperation(state, commands string) string {
|
||||||
|
return buildWriteStateOperation("set", state, commands)
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildWriteStateOperation(operation, state, commands string) string {
|
||||||
|
return fmt.Sprintf("d.init\n%s %s\n%s\nset %s\n", operation, state, commands, state)
|
||||||
|
}
|
||||||
|
|
||||||
|
func runSystemConfigCommand(command string) ([]byte, error) {
|
||||||
|
cmd := exec.Command(scutilPath)
|
||||||
|
cmd.Stdin = strings.NewReader(command)
|
||||||
|
out, err := cmd.Output()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("got error while running system configuration command: \"%s\", error: %s", command, err)
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
75
client/internal/dns/host_linux.go
Normal file
75
client/internal/dns/host_linux.go
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"github.com/netbirdio/netbird/iface"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultResolvConfPath = "/etc/resolv.conf"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
netbirdManager osManagerType = iota
|
||||||
|
fileManager
|
||||||
|
networkManager
|
||||||
|
systemdManager
|
||||||
|
resolvConfManager
|
||||||
|
)
|
||||||
|
|
||||||
|
type osManagerType int
|
||||||
|
|
||||||
|
func newHostManager(wgInterface *iface.WGIface) (hostManager, error) {
|
||||||
|
osManager, err := getOSDNSManagerType()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf("discovered mode is: %d", osManager)
|
||||||
|
switch osManager {
|
||||||
|
case networkManager:
|
||||||
|
return newNetworkManagerDbusConfigurator(wgInterface)
|
||||||
|
case systemdManager:
|
||||||
|
return newSystemdDbusConfigurator(wgInterface)
|
||||||
|
default:
|
||||||
|
return newFileConfigurator()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getOSDNSManagerType() (osManagerType, error) {
|
||||||
|
|
||||||
|
file, err := os.Open(defaultResolvConfPath)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("unable to open %s for checking owner, got error: %s", defaultResolvConfPath, err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(file)
|
||||||
|
for scanner.Scan() {
|
||||||
|
text := scanner.Text()
|
||||||
|
if len(text) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if text[0] != '#' {
|
||||||
|
return fileManager, nil
|
||||||
|
}
|
||||||
|
if strings.Contains(text, fileGeneratedResolvConfContentHeader) {
|
||||||
|
return netbirdManager, nil
|
||||||
|
}
|
||||||
|
if strings.Contains(text, "NetworkManager") && isDbusListenerRunning(networkManagerDest, networkManagerDbusObjectNode) && isNetworkManagerSupported() {
|
||||||
|
log.Debugf("is nm running on supported v? %t", isNetworkManagerSupportedVersion())
|
||||||
|
return networkManager, nil
|
||||||
|
}
|
||||||
|
if strings.Contains(text, "systemd-resolved") && isDbusListenerRunning(systemdResolvedDest, systemdDbusObjectNode) {
|
||||||
|
return systemdManager, nil
|
||||||
|
}
|
||||||
|
if strings.Contains(text, "resolvconf") {
|
||||||
|
return resolvConfManager, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fileManager, nil
|
||||||
|
}
|
||||||
260
client/internal/dns/host_windows.go
Normal file
260
client/internal/dns/host_windows.go
Normal file
@@ -0,0 +1,260 @@
|
|||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/netbirdio/netbird/iface"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"golang.org/x/sys/windows/registry"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
dnsPolicyConfigMatchPath = "SYSTEM\\CurrentControlSet\\Services\\Dnscache\\Parameters\\DnsPolicyConfig\\NetBird-Match"
|
||||||
|
dnsPolicyConfigVersionKey = "Version"
|
||||||
|
dnsPolicyConfigVersionValue = 2
|
||||||
|
dnsPolicyConfigNameKey = "Name"
|
||||||
|
dnsPolicyConfigGenericDNSServersKey = "GenericDNSServers"
|
||||||
|
dnsPolicyConfigConfigOptionsKey = "ConfigOptions"
|
||||||
|
dnsPolicyConfigConfigOptionsValue = 0x8
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
interfaceConfigPath = "SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Interfaces"
|
||||||
|
interfaceConfigNameServerKey = "NameServer"
|
||||||
|
interfaceConfigSearchListKey = "SearchList"
|
||||||
|
tcpipParametersPath = "SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters"
|
||||||
|
)
|
||||||
|
|
||||||
|
type registryConfigurator struct {
|
||||||
|
guid string
|
||||||
|
routingAll bool
|
||||||
|
existingSearchDomains []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newHostManager(wgInterface *iface.WGIface) (hostManager, error) {
|
||||||
|
guid, err := wgInterface.GetInterfaceGUIDString()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return ®istryConfigurator{
|
||||||
|
guid: guid,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *registryConfigurator) applyDNSConfig(config hostDNSConfig) error {
|
||||||
|
var err error
|
||||||
|
if config.routeAll {
|
||||||
|
err = r.addDNSSetupForAll(config.serverIP)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else if r.routingAll {
|
||||||
|
err = r.deleteInterfaceRegistryKeyProperty(interfaceConfigNameServerKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
r.routingAll = false
|
||||||
|
log.Infof("removed %s as main DNS forwarder for this peer", config.serverIP)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
searchDomains []string
|
||||||
|
matchDomains []string
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, dConf := range config.domains {
|
||||||
|
if !dConf.matchOnly {
|
||||||
|
searchDomains = append(searchDomains, dConf.domain)
|
||||||
|
}
|
||||||
|
matchDomains = append(matchDomains, "."+dConf.domain)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(matchDomains) != 0 {
|
||||||
|
err = r.addDNSMatchPolicy(matchDomains, config.serverIP)
|
||||||
|
} else {
|
||||||
|
err = removeRegistryKeyFromDNSPolicyConfig(dnsPolicyConfigMatchPath)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = r.updateSearchDomains(searchDomains)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *registryConfigurator) addDNSSetupForAll(ip string) error {
|
||||||
|
err := r.setInterfaceRegistryKeyStringValue(interfaceConfigNameServerKey, ip)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("adding dns setup for all failed with error: %s", err)
|
||||||
|
}
|
||||||
|
r.routingAll = true
|
||||||
|
log.Infof("configured %s:53 as main DNS forwarder for this peer", ip)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *registryConfigurator) addDNSMatchPolicy(domains []string, ip string) error {
|
||||||
|
_, err := registry.OpenKey(registry.LOCAL_MACHINE, dnsPolicyConfigMatchPath, registry.QUERY_VALUE)
|
||||||
|
if err == nil {
|
||||||
|
err = registry.DeleteKey(registry.LOCAL_MACHINE, dnsPolicyConfigMatchPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to remove existing key from registry, key: HKEY_LOCAL_MACHINE\\%s, error: %s", dnsPolicyConfigMatchPath, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
regKey, _, err := registry.CreateKey(registry.LOCAL_MACHINE, dnsPolicyConfigMatchPath, registry.SET_VALUE)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to create registry key, key: HKEY_LOCAL_MACHINE\\%s, error: %s", dnsPolicyConfigMatchPath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = regKey.SetDWordValue(dnsPolicyConfigVersionKey, dnsPolicyConfigVersionValue)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to set registry value for %s, error: %s", dnsPolicyConfigVersionKey, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = regKey.SetStringsValue(dnsPolicyConfigNameKey, domains)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to set registry value for %s, error: %s", dnsPolicyConfigNameKey, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = regKey.SetStringValue(dnsPolicyConfigGenericDNSServersKey, ip)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to set registry value for %s, error: %s", dnsPolicyConfigGenericDNSServersKey, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = regKey.SetDWordValue(dnsPolicyConfigConfigOptionsKey, dnsPolicyConfigConfigOptionsValue)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to set registry value for %s, error: %s", dnsPolicyConfigConfigOptionsKey, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("added %d match domains to the state. Domain list: %s", len(domains), domains)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *registryConfigurator) restoreHostDNS() error {
|
||||||
|
err := removeRegistryKeyFromDNSPolicyConfig(dnsPolicyConfigMatchPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.updateSearchDomains([]string{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *registryConfigurator) updateSearchDomains(domains []string) error {
|
||||||
|
value, err := getLocalMachineRegistryKeyStringValue(tcpipParametersPath, interfaceConfigSearchListKey)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to get current search domains failed with error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
valueList := strings.Split(value, ",")
|
||||||
|
setExisting := false
|
||||||
|
if len(r.existingSearchDomains) == 0 {
|
||||||
|
r.existingSearchDomains = valueList
|
||||||
|
setExisting = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(domains) == 0 && setExisting {
|
||||||
|
log.Infof("added %d search domains to the registry. Domain list: %s", len(domains), domains)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
newList := append(r.existingSearchDomains, domains...)
|
||||||
|
|
||||||
|
err = setLocalMachineRegistryKeyStringValue(tcpipParametersPath, interfaceConfigSearchListKey, strings.Join(newList, ","))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("adding search domain failed with error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("updated the search domains in the registry with %d domains. Domain list: %s", len(domains), domains)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *registryConfigurator) setInterfaceRegistryKeyStringValue(key, value string) error {
|
||||||
|
regKey, err := r.getInterfaceRegistryKey()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer regKey.Close()
|
||||||
|
|
||||||
|
err = regKey.SetStringValue(key, value)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("applying key %s with value \"%s\" for interface failed with error: %s", key, value, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *registryConfigurator) deleteInterfaceRegistryKeyProperty(propertyKey string) error {
|
||||||
|
regKey, err := r.getInterfaceRegistryKey()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer regKey.Close()
|
||||||
|
|
||||||
|
err = regKey.DeleteValue(propertyKey)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("deleting registry key %s for interface failed with error: %s", propertyKey, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *registryConfigurator) getInterfaceRegistryKey() (registry.Key, error) {
|
||||||
|
var regKey registry.Key
|
||||||
|
|
||||||
|
regKeyPath := interfaceConfigPath + "\\" + r.guid
|
||||||
|
|
||||||
|
regKey, err := registry.OpenKey(registry.LOCAL_MACHINE, regKeyPath, registry.SET_VALUE)
|
||||||
|
if err != nil {
|
||||||
|
return regKey, fmt.Errorf("unable to open the interface registry key, key: HKEY_LOCAL_MACHINE\\%s, error: %s", regKeyPath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return regKey, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeRegistryKeyFromDNSPolicyConfig(regKeyPath string) error {
|
||||||
|
k, err := registry.OpenKey(registry.LOCAL_MACHINE, regKeyPath, registry.QUERY_VALUE)
|
||||||
|
if err == nil {
|
||||||
|
k.Close()
|
||||||
|
err = registry.DeleteKey(registry.LOCAL_MACHINE, regKeyPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to remove existing key from registry, key: HKEY_LOCAL_MACHINE\\%s, error: %s", regKeyPath, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getLocalMachineRegistryKeyStringValue(keyPath, key string) (string, error) {
|
||||||
|
regKey, err := registry.OpenKey(registry.LOCAL_MACHINE, keyPath, registry.QUERY_VALUE)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("unable to open existing key from registry, key path: HKEY_LOCAL_MACHINE\\%s, error: %s", keyPath, err)
|
||||||
|
}
|
||||||
|
defer regKey.Close()
|
||||||
|
|
||||||
|
val, _, err := regKey.GetStringValue(key)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("getting %s value for key path HKEY_LOCAL_MACHINE\\%s failed with error: %s", key, keyPath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return val, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setLocalMachineRegistryKeyStringValue(keyPath, key, value string) error {
|
||||||
|
regKey, err := registry.OpenKey(registry.LOCAL_MACHINE, keyPath, registry.SET_VALUE)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to open existing key from registry, key path: HKEY_LOCAL_MACHINE\\%s, error: %s", keyPath, err)
|
||||||
|
}
|
||||||
|
defer regKey.Close()
|
||||||
|
|
||||||
|
err = regKey.SetStringValue(key, value)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("setting %s value %s for key path HKEY_LOCAL_MACHINE\\%s failed with error: %s", key, value, keyPath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
66
client/internal/dns/local.go
Normal file
66
client/internal/dns/local.go
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
nbdns "github.com/netbirdio/netbird/dns"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
type localResolver struct {
|
||||||
|
registeredMap registrationMap
|
||||||
|
records sync.Map
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServeDNS handles a DNS request
|
||||||
|
func (d *localResolver) ServeDNS(w dns.ResponseWriter, r *dns.Msg) {
|
||||||
|
log.Debugf("received question: %#v\n", r.Question[0])
|
||||||
|
replyMessage := &dns.Msg{}
|
||||||
|
replyMessage.SetReply(r)
|
||||||
|
replyMessage.RecursionAvailable = true
|
||||||
|
replyMessage.Rcode = dns.RcodeSuccess
|
||||||
|
|
||||||
|
response := d.lookupRecord(r)
|
||||||
|
if response != nil {
|
||||||
|
replyMessage.Answer = append(replyMessage.Answer, response)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := w.WriteMsg(replyMessage)
|
||||||
|
if err != nil {
|
||||||
|
log.Debugf("got an error while writing the local resolver response, error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *localResolver) lookupRecord(r *dns.Msg) dns.RR {
|
||||||
|
question := r.Question[0]
|
||||||
|
record, found := d.records.Load(buildRecordKey(question.Name, question.Qclass, question.Qtype))
|
||||||
|
if !found {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return record.(dns.RR)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *localResolver) registerRecord(record nbdns.SimpleRecord) error {
|
||||||
|
fullRecord, err := dns.NewRR(record.String())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fullRecord.Header().Rdlength = record.Len()
|
||||||
|
|
||||||
|
header := fullRecord.Header()
|
||||||
|
d.records.Store(buildRecordKey(header.Name, header.Class, header.Rrtype), fullRecord)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *localResolver) deleteRecord(recordKey string) {
|
||||||
|
d.records.Delete(dns.Fqdn(recordKey))
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildRecordKey(name string, class, qType uint16) string {
|
||||||
|
key := fmt.Sprintf("%s_%d_%d", name, class, qType)
|
||||||
|
return key
|
||||||
|
}
|
||||||
86
client/internal/dns/local_test.go
Normal file
86
client/internal/dns/local_test.go
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
nbdns "github.com/netbirdio/netbird/dns"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLocalResolver_ServeDNS(t *testing.T) {
|
||||||
|
recordA := nbdns.SimpleRecord{
|
||||||
|
Name: "peera.netbird.cloud.",
|
||||||
|
Type: 1,
|
||||||
|
Class: nbdns.DefaultClass,
|
||||||
|
TTL: 300,
|
||||||
|
RData: "1.2.3.4",
|
||||||
|
}
|
||||||
|
|
||||||
|
recordCNAME := nbdns.SimpleRecord{
|
||||||
|
Name: "peerb.netbird.cloud.",
|
||||||
|
Type: 5,
|
||||||
|
Class: nbdns.DefaultClass,
|
||||||
|
TTL: 300,
|
||||||
|
RData: "www.netbird.io",
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
inputRecord nbdns.SimpleRecord
|
||||||
|
inputMSG *dns.Msg
|
||||||
|
responseShouldBeNil bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Should Resolve A Record",
|
||||||
|
inputRecord: recordA,
|
||||||
|
inputMSG: new(dns.Msg).SetQuestion(recordA.Name, dns.TypeA),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Should Resolve CNAME Record",
|
||||||
|
inputRecord: recordCNAME,
|
||||||
|
inputMSG: new(dns.Msg).SetQuestion(recordCNAME.Name, dns.TypeCNAME),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Should Not Write When Not Found A Record",
|
||||||
|
inputRecord: recordA,
|
||||||
|
inputMSG: new(dns.Msg).SetQuestion("not.found.com", dns.TypeA),
|
||||||
|
responseShouldBeNil: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
|
resolver := &localResolver{
|
||||||
|
registeredMap: make(registrationMap),
|
||||||
|
}
|
||||||
|
_ = resolver.registerRecord(testCase.inputRecord)
|
||||||
|
var responseMSG *dns.Msg
|
||||||
|
responseWriter := &mockResponseWriter{
|
||||||
|
WriteMsgFunc: func(m *dns.Msg) error {
|
||||||
|
responseMSG = m
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
resolver.ServeDNS(responseWriter, testCase.inputMSG)
|
||||||
|
|
||||||
|
if responseMSG == nil || len(responseMSG.Answer) == 0 {
|
||||||
|
if testCase.responseShouldBeNil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.Fatalf("should write a response message")
|
||||||
|
}
|
||||||
|
|
||||||
|
answerString := responseMSG.Answer[0].String()
|
||||||
|
if !strings.Contains(answerString, testCase.inputRecord.Name) {
|
||||||
|
t.Fatalf("answer doesn't contain the same domain name: \nWant: %s\nGot:%s", testCase.name, answerString)
|
||||||
|
}
|
||||||
|
if !strings.Contains(answerString, dns.Type(testCase.inputRecord.Type).String()) {
|
||||||
|
t.Fatalf("answer doesn't contain the correct type: \nWant: %s\nGot:%s", dns.Type(testCase.inputRecord.Type).String(), answerString)
|
||||||
|
}
|
||||||
|
if !strings.Contains(answerString, testCase.inputRecord.RData) {
|
||||||
|
t.Fatalf("answer doesn't contain the same address: \nWant: %s\nGot:%s", testCase.inputRecord.RData, answerString)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
35
client/internal/dns/mockServer.go
Normal file
35
client/internal/dns/mockServer.go
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
nbdns "github.com/netbirdio/netbird/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MockServer is the mock instance of a dns server
|
||||||
|
type MockServer struct {
|
||||||
|
StartFunc func()
|
||||||
|
StopFunc func()
|
||||||
|
UpdateDNSServerFunc func(serial uint64, update nbdns.Config) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start mock implementation of Start from Server interface
|
||||||
|
func (m *MockServer) Start() {
|
||||||
|
if m.StartFunc != nil {
|
||||||
|
m.StartFunc()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop mock implementation of Stop from Server interface
|
||||||
|
func (m *MockServer) Stop() {
|
||||||
|
if m.StopFunc != nil {
|
||||||
|
m.StopFunc()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateDNSServer mock implementation of UpdateDNSServer from Server interface
|
||||||
|
func (m *MockServer) UpdateDNSServer(serial uint64, update nbdns.Config) error {
|
||||||
|
if m.UpdateDNSServerFunc != nil {
|
||||||
|
return m.UpdateDNSServerFunc(serial, update)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("method UpdateDNSServer is not implemented")
|
||||||
|
}
|
||||||
25
client/internal/dns/mock_test.go
Normal file
25
client/internal/dns/mock_test.go
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
type mockResponseWriter struct {
|
||||||
|
WriteMsgFunc func(m *dns.Msg) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *mockResponseWriter) WriteMsg(m *dns.Msg) error {
|
||||||
|
if rw.WriteMsgFunc != nil {
|
||||||
|
return rw.WriteMsgFunc(m)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *mockResponseWriter) LocalAddr() net.Addr { return nil }
|
||||||
|
func (rw *mockResponseWriter) RemoteAddr() net.Addr { return nil }
|
||||||
|
func (rw *mockResponseWriter) Write([]byte) (int, error) { return 0, nil }
|
||||||
|
func (rw *mockResponseWriter) Close() error { return nil }
|
||||||
|
func (rw *mockResponseWriter) TsigStatus() error { return nil }
|
||||||
|
func (rw *mockResponseWriter) TsigTimersOnly(bool) {}
|
||||||
|
func (rw *mockResponseWriter) Hijack() {}
|
||||||
295
client/internal/dns/network_manager_linux.go
Normal file
295
client/internal/dns/network_manager_linux.go
Normal file
@@ -0,0 +1,295 @@
|
|||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"github.com/godbus/dbus/v5"
|
||||||
|
"github.com/hashicorp/go-version"
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
"github.com/netbirdio/netbird/iface"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"net/netip"
|
||||||
|
"regexp"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
networkManagerDest = "org.freedesktop.NetworkManager"
|
||||||
|
networkManagerDbusObjectNode = "/org/freedesktop/NetworkManager"
|
||||||
|
networkManagerDbusDNSManagerInterface = "org.freedesktop.NetworkManager.DnsManager"
|
||||||
|
networkManagerDbusDNSManagerObjectNode = networkManagerDbusObjectNode + "/DnsManager"
|
||||||
|
networkManagerDbusDNSManagerModeProperty = networkManagerDbusDNSManagerInterface + ".Mode"
|
||||||
|
networkManagerDbusDNSManagerRcManagerProperty = networkManagerDbusDNSManagerInterface + ".RcManager"
|
||||||
|
networkManagerDbusVersionProperty = "org.freedesktop.NetworkManager.Version"
|
||||||
|
networkManagerDbusGetDeviceByIPIfaceMethod = networkManagerDest + ".GetDeviceByIpIface"
|
||||||
|
networkManagerDbusDeviceInterface = "org.freedesktop.NetworkManager.Device"
|
||||||
|
networkManagerDbusDeviceGetAppliedConnectionMethod = networkManagerDbusDeviceInterface + ".GetAppliedConnection"
|
||||||
|
networkManagerDbusDeviceReapplyMethod = networkManagerDbusDeviceInterface + ".Reapply"
|
||||||
|
networkManagerDbusDeviceDeleteMethod = networkManagerDbusDeviceInterface + ".Delete"
|
||||||
|
networkManagerDbusDefaultBehaviorFlag networkManagerConfigBehavior = 0
|
||||||
|
networkManagerDbusIPv4Key = "ipv4"
|
||||||
|
networkManagerDbusIPv6Key = "ipv6"
|
||||||
|
networkManagerDbusDNSKey = "dns"
|
||||||
|
networkManagerDbusDNSSearchKey = "dns-search"
|
||||||
|
networkManagerDbusDNSPriorityKey = "dns-priority"
|
||||||
|
|
||||||
|
// dns priority doc https://wiki.gnome.org/Projects/NetworkManager/DNS
|
||||||
|
networkManagerDbusPrimaryDNSPriority int32 = -500
|
||||||
|
networkManagerDbusWithMatchDomainPriority int32 = 0
|
||||||
|
networkManagerDbusSearchDomainOnlyPriority int32 = 50
|
||||||
|
supportedNetworkManagerVersionConstraint = ">= 1.16, < 1.28"
|
||||||
|
)
|
||||||
|
|
||||||
|
type networkManagerDbusConfigurator struct {
|
||||||
|
dbusLinkObject dbus.ObjectPath
|
||||||
|
routingAll bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// the types below are based on dbus specification, each field is mapped to a dbus type
|
||||||
|
// see https://dbus.freedesktop.org/doc/dbus-specification.html#basic-types for more details on dbus types
|
||||||
|
// see https://networkmanager.dev/docs/api/latest/gdbus-org.freedesktop.NetworkManager.Device.html on Network Manager input types
|
||||||
|
|
||||||
|
// networkManagerConnSettings maps to a (a{sa{sv}}) dbus output from GetAppliedConnection and input for Reapply methods
|
||||||
|
type networkManagerConnSettings map[string]map[string]dbus.Variant
|
||||||
|
|
||||||
|
// networkManagerConfigVersion maps to a (t) dbus output from GetAppliedConnection and input for Reapply methods
|
||||||
|
type networkManagerConfigVersion uint64
|
||||||
|
|
||||||
|
// networkManagerConfigBehavior maps to a (u) dbus input for GetAppliedConnection and Reapply methods
|
||||||
|
type networkManagerConfigBehavior uint32
|
||||||
|
|
||||||
|
// cleanDeprecatedSettings cleans deprecated settings that still returned by
|
||||||
|
// the GetAppliedConnection methods but can't be reApplied
|
||||||
|
func (s networkManagerConnSettings) cleanDeprecatedSettings() {
|
||||||
|
for _, key := range []string{"addresses", "routes"} {
|
||||||
|
delete(s[networkManagerDbusIPv4Key], key)
|
||||||
|
delete(s[networkManagerDbusIPv6Key], key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newNetworkManagerDbusConfigurator(wgInterface *iface.WGIface) (hostManager, error) {
|
||||||
|
obj, closeConn, err := getDbusObject(networkManagerDest, networkManagerDbusObjectNode)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer closeConn()
|
||||||
|
var s string
|
||||||
|
err = obj.Call(networkManagerDbusGetDeviceByIPIfaceMethod, dbusDefaultFlag, wgInterface.GetName()).Store(&s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf("got network manager dbus Link Object: %s from net interface %s", s, wgInterface.GetName())
|
||||||
|
|
||||||
|
return &networkManagerDbusConfigurator{
|
||||||
|
dbusLinkObject: dbus.ObjectPath(s),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *networkManagerDbusConfigurator) applyDNSConfig(config hostDNSConfig) error {
|
||||||
|
connSettings, configVersion, err := n.getAppliedConnectionSettings()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("got an error while retrieving the applied connection settings, error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
connSettings.cleanDeprecatedSettings()
|
||||||
|
|
||||||
|
dnsIP := netip.MustParseAddr(config.serverIP)
|
||||||
|
convDNSIP := binary.LittleEndian.Uint32(dnsIP.AsSlice())
|
||||||
|
connSettings[networkManagerDbusIPv4Key][networkManagerDbusDNSKey] = dbus.MakeVariant([]uint32{convDNSIP})
|
||||||
|
var (
|
||||||
|
searchDomains []string
|
||||||
|
matchDomains []string
|
||||||
|
)
|
||||||
|
for _, dConf := range config.domains {
|
||||||
|
if dConf.matchOnly {
|
||||||
|
matchDomains = append(matchDomains, "~."+dns.Fqdn(dConf.domain))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
searchDomains = append(searchDomains, dns.Fqdn(dConf.domain))
|
||||||
|
}
|
||||||
|
|
||||||
|
newDomainList := append(searchDomains, matchDomains...)
|
||||||
|
|
||||||
|
priority := networkManagerDbusSearchDomainOnlyPriority
|
||||||
|
switch {
|
||||||
|
case config.routeAll:
|
||||||
|
priority = networkManagerDbusPrimaryDNSPriority
|
||||||
|
newDomainList = append(newDomainList, "~.")
|
||||||
|
if !n.routingAll {
|
||||||
|
log.Infof("configured %s:%d as main DNS forwarder for this peer", config.serverIP, config.serverPort)
|
||||||
|
}
|
||||||
|
case len(matchDomains) > 0:
|
||||||
|
priority = networkManagerDbusWithMatchDomainPriority
|
||||||
|
}
|
||||||
|
|
||||||
|
if priority != networkManagerDbusPrimaryDNSPriority && n.routingAll {
|
||||||
|
log.Infof("removing %s:%d as main DNS forwarder for this peer", config.serverIP, config.serverPort)
|
||||||
|
n.routingAll = false
|
||||||
|
}
|
||||||
|
|
||||||
|
connSettings[networkManagerDbusIPv4Key][networkManagerDbusDNSPriorityKey] = dbus.MakeVariant(priority)
|
||||||
|
connSettings[networkManagerDbusIPv4Key][networkManagerDbusDNSSearchKey] = dbus.MakeVariant(newDomainList)
|
||||||
|
|
||||||
|
log.Infof("adding %d search domains and %d match domains. Search list: %s , Match list: %s", len(searchDomains), len(matchDomains), searchDomains, matchDomains)
|
||||||
|
err = n.reApplyConnectionSettings(connSettings, configVersion)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("got an error while reapplying the connection with new settings, error: %s", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *networkManagerDbusConfigurator) restoreHostDNS() error {
|
||||||
|
// once the interface is gone network manager cleans all config associated with it
|
||||||
|
return n.deleteConnectionSettings()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *networkManagerDbusConfigurator) getAppliedConnectionSettings() (networkManagerConnSettings, networkManagerConfigVersion, error) {
|
||||||
|
obj, closeConn, err := getDbusObject(networkManagerDest, n.dbusLinkObject)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, fmt.Errorf("got error while attempting to retrieve the applied connection settings, err: %s", err)
|
||||||
|
}
|
||||||
|
defer closeConn()
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
var (
|
||||||
|
connSettings networkManagerConnSettings
|
||||||
|
configVersion networkManagerConfigVersion
|
||||||
|
)
|
||||||
|
|
||||||
|
err = obj.CallWithContext(ctx, networkManagerDbusDeviceGetAppliedConnectionMethod, dbusDefaultFlag,
|
||||||
|
networkManagerDbusDefaultBehaviorFlag).Store(&connSettings, &configVersion)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, fmt.Errorf("got error while calling GetAppliedConnection method with context, err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return connSettings, configVersion, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *networkManagerDbusConfigurator) reApplyConnectionSettings(connSettings networkManagerConnSettings, configVersion networkManagerConfigVersion) error {
|
||||||
|
obj, closeConn, err := getDbusObject(networkManagerDest, n.dbusLinkObject)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("got error while attempting to retrieve the applied connection settings, err: %s", err)
|
||||||
|
}
|
||||||
|
defer closeConn()
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
err = obj.CallWithContext(ctx, networkManagerDbusDeviceReapplyMethod, dbusDefaultFlag,
|
||||||
|
connSettings, configVersion, networkManagerDbusDefaultBehaviorFlag).Store()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("got error while calling ReApply method with context, err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *networkManagerDbusConfigurator) deleteConnectionSettings() error {
|
||||||
|
obj, closeConn, err := getDbusObject(networkManagerDest, n.dbusLinkObject)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("got error while attempting to retrieve the applied connection settings, err: %s", err)
|
||||||
|
}
|
||||||
|
defer closeConn()
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
err = obj.CallWithContext(ctx, networkManagerDbusDeviceDeleteMethod, dbusDefaultFlag).Store()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("got error while calling delete method with context, err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isNetworkManagerSupported() bool {
|
||||||
|
return isNetworkManagerSupportedVersion() && isNetworkManagerSupportedMode()
|
||||||
|
}
|
||||||
|
|
||||||
|
func isNetworkManagerSupportedMode() bool {
|
||||||
|
var mode string
|
||||||
|
err := getNetworkManagerDNSProperty(networkManagerDbusDNSManagerModeProperty, &mode)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
switch mode {
|
||||||
|
case "dnsmasq", "unbound", "systemd-resolved":
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
var rcManager string
|
||||||
|
err = getNetworkManagerDNSProperty(networkManagerDbusDNSManagerRcManagerProperty, &rcManager)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if rcManager == "unmanaged" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func getNetworkManagerDNSProperty(property string, store any) error {
|
||||||
|
obj, closeConn, err := getDbusObject(networkManagerDest, networkManagerDbusDNSManagerObjectNode)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("got error while attempting to retrieve the network manager dns manager object, error: %s", err)
|
||||||
|
}
|
||||||
|
defer closeConn()
|
||||||
|
|
||||||
|
v, e := obj.GetProperty(property)
|
||||||
|
if e != nil {
|
||||||
|
return fmt.Errorf("got an error getting property %s: %v", property, e)
|
||||||
|
}
|
||||||
|
|
||||||
|
return v.Store(store)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isNetworkManagerSupportedVersion() bool {
|
||||||
|
obj, closeConn, err := getDbusObject(networkManagerDest, networkManagerDbusObjectNode)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("got error while attempting to get the network manager object, err: %s", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
defer closeConn()
|
||||||
|
|
||||||
|
value, err := obj.GetProperty(networkManagerDbusVersionProperty)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("unable to retrieve network manager mode, got error: %s", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
versionValue, err := parseVersion(value.Value().(string))
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
constraints, err := version.NewConstraint(supportedNetworkManagerVersionConstraint)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return constraints.Check(versionValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseVersion(inputVersion string) (*version.Version, error) {
|
||||||
|
reg, err := regexp.Compile(version.SemverRegexpRaw)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if inputVersion == "" || !reg.MatchString(inputVersion) {
|
||||||
|
return nil, fmt.Errorf("couldn't parse the provided version: Not SemVer")
|
||||||
|
}
|
||||||
|
|
||||||
|
verObj, err := version.NewVersion(inputVersion)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return verObj, nil
|
||||||
|
}
|
||||||
333
client/internal/dns/server.go
Normal file
333
client/internal/dns/server.go
Normal file
@@ -0,0 +1,333 @@
|
|||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
nbdns "github.com/netbirdio/netbird/dns"
|
||||||
|
"github.com/netbirdio/netbird/iface"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
"runtime"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
port = 53
|
||||||
|
customPort = 5053
|
||||||
|
defaultIP = "127.0.0.1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Server is a dns server interface
|
||||||
|
type Server interface {
|
||||||
|
Start()
|
||||||
|
Stop()
|
||||||
|
UpdateDNSServer(serial uint64, update nbdns.Config) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultServer dns server object
|
||||||
|
type DefaultServer struct {
|
||||||
|
ctx context.Context
|
||||||
|
stop context.CancelFunc
|
||||||
|
mux sync.Mutex
|
||||||
|
server *dns.Server
|
||||||
|
dnsMux *dns.ServeMux
|
||||||
|
dnsMuxMap registrationMap
|
||||||
|
localResolver *localResolver
|
||||||
|
wgInterface *iface.WGIface
|
||||||
|
hostManager hostManager
|
||||||
|
updateSerial uint64
|
||||||
|
listenerIsRunning bool
|
||||||
|
runtimePort int
|
||||||
|
runtimeIP string
|
||||||
|
}
|
||||||
|
|
||||||
|
type registrationMap map[string]struct{}
|
||||||
|
|
||||||
|
type muxUpdate struct {
|
||||||
|
domain string
|
||||||
|
handler dns.Handler
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDefaultServer returns a new dns server
|
||||||
|
func NewDefaultServer(ctx context.Context, wgInterface *iface.WGIface) (*DefaultServer, error) {
|
||||||
|
mux := dns.NewServeMux()
|
||||||
|
listenIP := defaultIP
|
||||||
|
if runtime.GOOS != "darwin" && wgInterface != nil {
|
||||||
|
listenIP = wgInterface.GetAddress().IP.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
dnsServer := &dns.Server{
|
||||||
|
Addr: fmt.Sprintf("%s:%d", listenIP, port),
|
||||||
|
Net: "udp",
|
||||||
|
Handler: mux,
|
||||||
|
UDPSize: 65535,
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, stop := context.WithCancel(ctx)
|
||||||
|
|
||||||
|
defaultServer := &DefaultServer{
|
||||||
|
ctx: ctx,
|
||||||
|
stop: stop,
|
||||||
|
server: dnsServer,
|
||||||
|
dnsMux: mux,
|
||||||
|
dnsMuxMap: make(registrationMap),
|
||||||
|
localResolver: &localResolver{
|
||||||
|
registeredMap: make(registrationMap),
|
||||||
|
},
|
||||||
|
wgInterface: wgInterface,
|
||||||
|
runtimePort: port,
|
||||||
|
runtimeIP: listenIP,
|
||||||
|
}
|
||||||
|
|
||||||
|
hostmanager, err := newHostManager(wgInterface)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defaultServer.hostManager = hostmanager
|
||||||
|
return defaultServer, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start runs the listener in a go routine
|
||||||
|
func (s *DefaultServer) Start() {
|
||||||
|
s.runtimePort = port
|
||||||
|
udpAddr := net.UDPAddrFromAddrPort(netip.MustParseAddrPort(s.server.Addr))
|
||||||
|
probeListener, err := net.ListenUDP("udp", udpAddr)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("using a custom port for dns server")
|
||||||
|
s.runtimePort = customPort
|
||||||
|
s.server.Addr = fmt.Sprintf("%s:%d", s.runtimeIP, customPort)
|
||||||
|
} else {
|
||||||
|
err = probeListener.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("got an error closing the probe listener, error: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf("starting dns on %s", s.server.Addr)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
s.setListenerStatus(true)
|
||||||
|
defer s.setListenerStatus(false)
|
||||||
|
|
||||||
|
err = s.server.ListenAndServe()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("dns server running with %d port returned an error: %v. Will not retry", s.runtimePort, err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DefaultServer) setListenerStatus(running bool) {
|
||||||
|
s.listenerIsRunning = running
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop stops the server
|
||||||
|
func (s *DefaultServer) Stop() {
|
||||||
|
s.mux.Lock()
|
||||||
|
defer s.mux.Unlock()
|
||||||
|
s.stop()
|
||||||
|
|
||||||
|
err := s.hostManager.restoreHostDNS()
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = s.stopListener()
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DefaultServer) stopListener() error {
|
||||||
|
if !s.listenerIsRunning {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
err := s.server.ShutdownContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("stopping dns server listener returned an error: %v", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateDNSServer processes an update received from the management service
|
||||||
|
func (s *DefaultServer) UpdateDNSServer(serial uint64, update nbdns.Config) error {
|
||||||
|
select {
|
||||||
|
case <-s.ctx.Done():
|
||||||
|
log.Infof("not updating DNS server as context is closed")
|
||||||
|
return s.ctx.Err()
|
||||||
|
default:
|
||||||
|
if serial < s.updateSerial {
|
||||||
|
return fmt.Errorf("not applying dns update, error: "+
|
||||||
|
"network update is %d behind the last applied update", s.updateSerial-serial)
|
||||||
|
}
|
||||||
|
s.mux.Lock()
|
||||||
|
defer s.mux.Unlock()
|
||||||
|
|
||||||
|
// is the service should be disabled, we stop the listener
|
||||||
|
// and proceed with a regular update to clean up the handlers and records
|
||||||
|
if !update.ServiceEnable {
|
||||||
|
err := s.stopListener()
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
} else if !s.listenerIsRunning {
|
||||||
|
s.Start()
|
||||||
|
}
|
||||||
|
|
||||||
|
localMuxUpdates, localRecords, err := s.buildLocalHandlerUpdate(update.CustomZones)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("not applying dns update, error: %v", err)
|
||||||
|
}
|
||||||
|
upstreamMuxUpdates, err := s.buildUpstreamHandlerUpdate(update.NameServerGroups)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("not applying dns update, error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
muxUpdates := append(localMuxUpdates, upstreamMuxUpdates...)
|
||||||
|
|
||||||
|
s.updateMux(muxUpdates)
|
||||||
|
s.updateLocalResolver(localRecords)
|
||||||
|
|
||||||
|
err = s.hostManager.applyDNSConfig(dnsConfigToHostDNSConfig(update, s.runtimeIP, s.runtimePort))
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.updateSerial = serial
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DefaultServer) buildLocalHandlerUpdate(customZones []nbdns.CustomZone) ([]muxUpdate, map[string]nbdns.SimpleRecord, error) {
|
||||||
|
var muxUpdates []muxUpdate
|
||||||
|
localRecords := make(map[string]nbdns.SimpleRecord, 0)
|
||||||
|
|
||||||
|
for _, customZone := range customZones {
|
||||||
|
|
||||||
|
if len(customZone.Records) == 0 {
|
||||||
|
return nil, nil, fmt.Errorf("received an empty list of records")
|
||||||
|
}
|
||||||
|
|
||||||
|
muxUpdates = append(muxUpdates, muxUpdate{
|
||||||
|
domain: customZone.Domain,
|
||||||
|
handler: s.localResolver,
|
||||||
|
})
|
||||||
|
|
||||||
|
for _, record := range customZone.Records {
|
||||||
|
var class uint16 = dns.ClassINET
|
||||||
|
if record.Class != nbdns.DefaultClass {
|
||||||
|
return nil, nil, fmt.Errorf("received an invalid class type: %s", record.Class)
|
||||||
|
}
|
||||||
|
key := buildRecordKey(record.Name, class, uint16(record.Type))
|
||||||
|
localRecords[key] = record
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return muxUpdates, localRecords, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DefaultServer) buildUpstreamHandlerUpdate(nameServerGroups []*nbdns.NameServerGroup) ([]muxUpdate, error) {
|
||||||
|
var muxUpdates []muxUpdate
|
||||||
|
for _, nsGroup := range nameServerGroups {
|
||||||
|
if len(nsGroup.NameServers) == 0 {
|
||||||
|
return nil, fmt.Errorf("received a nameserver group with empty nameserver list")
|
||||||
|
}
|
||||||
|
handler := &upstreamResolver{
|
||||||
|
parentCTX: s.ctx,
|
||||||
|
upstreamClient: &dns.Client{},
|
||||||
|
upstreamTimeout: defaultUpstreamTimeout,
|
||||||
|
}
|
||||||
|
for _, ns := range nsGroup.NameServers {
|
||||||
|
if ns.NSType != nbdns.UDPNameServerType {
|
||||||
|
log.Warnf("skiping nameserver %s with type %s, this peer supports only %s",
|
||||||
|
ns.IP.String(), ns.NSType.String(), nbdns.UDPNameServerType.String())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
handler.upstreamServers = append(handler.upstreamServers, getNSHostPort(ns))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(handler.upstreamServers) == 0 {
|
||||||
|
log.Errorf("received a nameserver group with an invalid nameserver list")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if nsGroup.Primary {
|
||||||
|
muxUpdates = append(muxUpdates, muxUpdate{
|
||||||
|
domain: nbdns.RootZone,
|
||||||
|
handler: handler,
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(nsGroup.Domains) == 0 {
|
||||||
|
return nil, fmt.Errorf("received a non primary nameserver group with an empty domain list")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, domain := range nsGroup.Domains {
|
||||||
|
if domain == "" {
|
||||||
|
return nil, fmt.Errorf("received a nameserver group with an empty domain element")
|
||||||
|
}
|
||||||
|
muxUpdates = append(muxUpdates, muxUpdate{
|
||||||
|
domain: domain,
|
||||||
|
handler: handler,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return muxUpdates, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DefaultServer) updateMux(muxUpdates []muxUpdate) {
|
||||||
|
muxUpdateMap := make(registrationMap)
|
||||||
|
|
||||||
|
for _, update := range muxUpdates {
|
||||||
|
s.registerMux(update.domain, update.handler)
|
||||||
|
muxUpdateMap[update.domain] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
for key := range s.dnsMuxMap {
|
||||||
|
_, found := muxUpdateMap[key]
|
||||||
|
if !found {
|
||||||
|
s.deregisterMux(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s.dnsMuxMap = muxUpdateMap
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DefaultServer) updateLocalResolver(update map[string]nbdns.SimpleRecord) {
|
||||||
|
for key := range s.localResolver.registeredMap {
|
||||||
|
_, found := update[key]
|
||||||
|
if !found {
|
||||||
|
s.localResolver.deleteRecord(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedMap := make(registrationMap)
|
||||||
|
for key, record := range update {
|
||||||
|
err := s.localResolver.registerRecord(record)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("got an error while registering the record (%s), error: %v", record.String(), err)
|
||||||
|
}
|
||||||
|
updatedMap[key] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
s.localResolver.registeredMap = updatedMap
|
||||||
|
}
|
||||||
|
|
||||||
|
func getNSHostPort(ns nbdns.NameServer) string {
|
||||||
|
return fmt.Sprintf("%s:%d", ns.IP.String(), ns.Port)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DefaultServer) registerMux(pattern string, handler dns.Handler) {
|
||||||
|
s.dnsMux.Handle(pattern, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DefaultServer) deregisterMux(pattern string) {
|
||||||
|
s.dnsMux.HandleRemove(pattern)
|
||||||
|
}
|
||||||
320
client/internal/dns/server_test.go
Normal file
320
client/internal/dns/server_test.go
Normal file
@@ -0,0 +1,320 @@
|
|||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
nbdns "github.com/netbirdio/netbird/dns"
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var zoneRecords = []nbdns.SimpleRecord{
|
||||||
|
{
|
||||||
|
Name: "peera.netbird.cloud",
|
||||||
|
Type: 1,
|
||||||
|
Class: nbdns.DefaultClass,
|
||||||
|
TTL: 300,
|
||||||
|
RData: "1.2.3.4",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpdateDNSServer(t *testing.T) {
|
||||||
|
|
||||||
|
nameServers := []nbdns.NameServer{
|
||||||
|
{
|
||||||
|
IP: netip.MustParseAddr("8.8.8.8"),
|
||||||
|
NSType: nbdns.UDPNameServerType,
|
||||||
|
Port: 53,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
IP: netip.MustParseAddr("8.8.4.4"),
|
||||||
|
NSType: nbdns.UDPNameServerType,
|
||||||
|
Port: 53,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
initUpstreamMap registrationMap
|
||||||
|
initLocalMap registrationMap
|
||||||
|
initSerial uint64
|
||||||
|
inputSerial uint64
|
||||||
|
inputUpdate nbdns.Config
|
||||||
|
shouldFail bool
|
||||||
|
expectedUpstreamMap registrationMap
|
||||||
|
expectedLocalMap registrationMap
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Initial Config Should Succeed",
|
||||||
|
initLocalMap: make(registrationMap),
|
||||||
|
initUpstreamMap: make(registrationMap),
|
||||||
|
initSerial: 0,
|
||||||
|
inputSerial: 1,
|
||||||
|
inputUpdate: nbdns.Config{
|
||||||
|
ServiceEnable: true,
|
||||||
|
CustomZones: []nbdns.CustomZone{
|
||||||
|
{
|
||||||
|
Domain: "netbird.cloud",
|
||||||
|
Records: zoneRecords,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
NameServerGroups: []*nbdns.NameServerGroup{
|
||||||
|
{
|
||||||
|
Domains: []string{"netbird.io"},
|
||||||
|
NameServers: nameServers,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
NameServers: nameServers,
|
||||||
|
Primary: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedUpstreamMap: registrationMap{"netbird.io": struct{}{}, "netbird.cloud": struct{}{}, nbdns.RootZone: struct{}{}},
|
||||||
|
expectedLocalMap: registrationMap{buildRecordKey(zoneRecords[0].Name, 1, 1): struct{}{}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "New Config Should Succeed",
|
||||||
|
initLocalMap: registrationMap{"netbird.cloud": struct{}{}},
|
||||||
|
initUpstreamMap: registrationMap{buildRecordKey(zoneRecords[0].Name, 1, 1): struct{}{}},
|
||||||
|
initSerial: 0,
|
||||||
|
inputSerial: 1,
|
||||||
|
inputUpdate: nbdns.Config{
|
||||||
|
ServiceEnable: true,
|
||||||
|
CustomZones: []nbdns.CustomZone{
|
||||||
|
{
|
||||||
|
Domain: "netbird.cloud",
|
||||||
|
Records: zoneRecords,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
NameServerGroups: []*nbdns.NameServerGroup{
|
||||||
|
{
|
||||||
|
Domains: []string{"netbird.io"},
|
||||||
|
NameServers: nameServers,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedUpstreamMap: registrationMap{"netbird.io": struct{}{}, "netbird.cloud": struct{}{}},
|
||||||
|
expectedLocalMap: registrationMap{buildRecordKey(zoneRecords[0].Name, 1, 1): struct{}{}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Smaller Config Serial Should Be Skipped",
|
||||||
|
initLocalMap: make(registrationMap),
|
||||||
|
initUpstreamMap: make(registrationMap),
|
||||||
|
initSerial: 2,
|
||||||
|
inputSerial: 1,
|
||||||
|
shouldFail: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Empty NS Group Domain Or Not Primary Element Should Fail",
|
||||||
|
initLocalMap: make(registrationMap),
|
||||||
|
initUpstreamMap: make(registrationMap),
|
||||||
|
initSerial: 0,
|
||||||
|
inputSerial: 1,
|
||||||
|
inputUpdate: nbdns.Config{
|
||||||
|
ServiceEnable: true,
|
||||||
|
CustomZones: []nbdns.CustomZone{
|
||||||
|
{
|
||||||
|
Domain: "netbird.cloud",
|
||||||
|
Records: zoneRecords,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
NameServerGroups: []*nbdns.NameServerGroup{
|
||||||
|
{
|
||||||
|
NameServers: nameServers,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
shouldFail: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Invalid NS Group Nameservers list Should Fail",
|
||||||
|
initLocalMap: make(registrationMap),
|
||||||
|
initUpstreamMap: make(registrationMap),
|
||||||
|
initSerial: 0,
|
||||||
|
inputSerial: 1,
|
||||||
|
inputUpdate: nbdns.Config{
|
||||||
|
ServiceEnable: true,
|
||||||
|
CustomZones: []nbdns.CustomZone{
|
||||||
|
{
|
||||||
|
Domain: "netbird.cloud",
|
||||||
|
Records: zoneRecords,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
NameServerGroups: []*nbdns.NameServerGroup{
|
||||||
|
{
|
||||||
|
NameServers: nameServers,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
shouldFail: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Invalid Custom Zone Records list Should Fail",
|
||||||
|
initLocalMap: make(registrationMap),
|
||||||
|
initUpstreamMap: make(registrationMap),
|
||||||
|
initSerial: 0,
|
||||||
|
inputSerial: 1,
|
||||||
|
inputUpdate: nbdns.Config{
|
||||||
|
ServiceEnable: true,
|
||||||
|
CustomZones: []nbdns.CustomZone{
|
||||||
|
{
|
||||||
|
Domain: "netbird.cloud",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
NameServerGroups: []*nbdns.NameServerGroup{
|
||||||
|
{
|
||||||
|
NameServers: nameServers,
|
||||||
|
Primary: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
shouldFail: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Empty Config Should Succeed and Clean Maps",
|
||||||
|
initLocalMap: registrationMap{"netbird.cloud": struct{}{}},
|
||||||
|
initUpstreamMap: registrationMap{zoneRecords[0].Name: struct{}{}},
|
||||||
|
initSerial: 0,
|
||||||
|
inputSerial: 1,
|
||||||
|
inputUpdate: nbdns.Config{ServiceEnable: true},
|
||||||
|
expectedUpstreamMap: make(registrationMap),
|
||||||
|
expectedLocalMap: make(registrationMap),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
|
dnsServer := getDefaultServerWithNoHostManager("127.0.0.1")
|
||||||
|
|
||||||
|
dnsServer.hostManager = newNoopHostMocker()
|
||||||
|
|
||||||
|
dnsServer.dnsMuxMap = testCase.initUpstreamMap
|
||||||
|
dnsServer.localResolver.registeredMap = testCase.initLocalMap
|
||||||
|
dnsServer.updateSerial = testCase.initSerial
|
||||||
|
// pretend we are running
|
||||||
|
dnsServer.listenerIsRunning = true
|
||||||
|
|
||||||
|
err := dnsServer.UpdateDNSServer(testCase.inputSerial, testCase.inputUpdate)
|
||||||
|
if err != nil {
|
||||||
|
if testCase.shouldFail {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.Fatalf("update dns server should not fail, got error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(dnsServer.dnsMuxMap) != len(testCase.expectedUpstreamMap) {
|
||||||
|
t.Fatalf("update upstream failed, map size is different than expected, want %d, got %d", len(testCase.expectedUpstreamMap), len(dnsServer.dnsMuxMap))
|
||||||
|
}
|
||||||
|
|
||||||
|
for key := range testCase.expectedUpstreamMap {
|
||||||
|
_, found := dnsServer.dnsMuxMap[key]
|
||||||
|
if !found {
|
||||||
|
t.Fatalf("update upstream failed, key %s was not found in the dnsMuxMap: %#v", key, dnsServer.dnsMuxMap)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(dnsServer.localResolver.registeredMap) != len(testCase.expectedLocalMap) {
|
||||||
|
t.Fatalf("update local failed, registered map size is different than expected, want %d, got %d", len(testCase.expectedLocalMap), len(dnsServer.localResolver.registeredMap))
|
||||||
|
}
|
||||||
|
|
||||||
|
for key := range testCase.expectedLocalMap {
|
||||||
|
_, found := dnsServer.localResolver.registeredMap[key]
|
||||||
|
if !found {
|
||||||
|
t.Fatalf("update local failed, key %s was not found in the localResolver.registeredMap: %#v", key, dnsServer.localResolver.registeredMap)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDNSServerStartStop(t *testing.T) {
|
||||||
|
dnsServer := getDefaultServerWithNoHostManager("127.0.0.1")
|
||||||
|
|
||||||
|
if runtime.GOOS == "windows" && os.Getenv("CI") == "true" {
|
||||||
|
// todo review why this test is not working only on github actions workflows
|
||||||
|
t.Skip("skipping test in Windows CI workflows.")
|
||||||
|
}
|
||||||
|
|
||||||
|
dnsServer.hostManager = newNoopHostMocker()
|
||||||
|
|
||||||
|
dnsServer.Start()
|
||||||
|
|
||||||
|
err := dnsServer.localResolver.registerRecord(zoneRecords[0])
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
dnsServer.dnsMux.Handle("netbird.cloud", dnsServer.localResolver)
|
||||||
|
|
||||||
|
resolver := &net.Resolver{
|
||||||
|
PreferGo: true,
|
||||||
|
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
|
||||||
|
d := net.Dialer{
|
||||||
|
Timeout: time.Second * 5,
|
||||||
|
}
|
||||||
|
addr := fmt.Sprintf("127.0.0.1:%d", port)
|
||||||
|
conn, err := d.DialContext(ctx, network, addr)
|
||||||
|
if err != nil {
|
||||||
|
t.Log(err)
|
||||||
|
// retry test before exit, for slower systems
|
||||||
|
return d.DialContext(ctx, network, addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return conn, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
ips, err := resolver.LookupHost(context.Background(), zoneRecords[0].Name)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to connect to the server, error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Log(ips)
|
||||||
|
|
||||||
|
if ips[0] != zoneRecords[0].RData {
|
||||||
|
t.Fatalf("got a different IP from the server: want %s, got %s", zoneRecords[0].RData, ips[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
dnsServer.Stop()
|
||||||
|
ctx, cancel := context.WithTimeout(context.TODO(), time.Second*1)
|
||||||
|
defer cancel()
|
||||||
|
_, err = resolver.LookupHost(ctx, zoneRecords[0].Name)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("we should encounter an error when querying a stopped server")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDefaultServerWithNoHostManager(ip string) *DefaultServer {
|
||||||
|
mux := dns.NewServeMux()
|
||||||
|
listenIP := defaultIP
|
||||||
|
if ip != "" {
|
||||||
|
listenIP = ip
|
||||||
|
}
|
||||||
|
|
||||||
|
dnsServer := &dns.Server{
|
||||||
|
Addr: fmt.Sprintf("%s:%d", ip, port),
|
||||||
|
Net: "udp",
|
||||||
|
Handler: mux,
|
||||||
|
UDPSize: 65535,
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, stop := context.WithCancel(context.TODO())
|
||||||
|
|
||||||
|
return &DefaultServer{
|
||||||
|
ctx: ctx,
|
||||||
|
stop: stop,
|
||||||
|
server: dnsServer,
|
||||||
|
dnsMux: mux,
|
||||||
|
dnsMuxMap: make(registrationMap),
|
||||||
|
localResolver: &localResolver{
|
||||||
|
registeredMap: make(registrationMap),
|
||||||
|
},
|
||||||
|
runtimePort: port,
|
||||||
|
runtimeIP: listenIP,
|
||||||
|
}
|
||||||
|
}
|
||||||
185
client/internal/dns/systemd_linux.go
Normal file
185
client/internal/dns/systemd_linux.go
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"github.com/godbus/dbus/v5"
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
nbdns "github.com/netbirdio/netbird/dns"
|
||||||
|
"github.com/netbirdio/netbird/iface"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
systemdDbusManagerInterface = "org.freedesktop.resolve1.Manager"
|
||||||
|
systemdResolvedDest = "org.freedesktop.resolve1"
|
||||||
|
systemdDbusObjectNode = "/org/freedesktop/resolve1"
|
||||||
|
systemdDbusGetLinkMethod = systemdDbusManagerInterface + ".GetLink"
|
||||||
|
systemdDbusFlushCachesMethod = systemdDbusManagerInterface + ".FlushCaches"
|
||||||
|
systemdDbusLinkInterface = "org.freedesktop.resolve1.Link"
|
||||||
|
systemdDbusRevertMethodSuffix = systemdDbusLinkInterface + ".Revert"
|
||||||
|
systemdDbusSetDNSMethodSuffix = systemdDbusLinkInterface + ".SetDNS"
|
||||||
|
systemdDbusSetDefaultRouteMethodSuffix = systemdDbusLinkInterface + ".SetDefaultRoute"
|
||||||
|
systemdDbusSetDomainsMethodSuffix = systemdDbusLinkInterface + ".SetDomains"
|
||||||
|
)
|
||||||
|
|
||||||
|
type systemdDbusConfigurator struct {
|
||||||
|
dbusLinkObject dbus.ObjectPath
|
||||||
|
routingAll bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// the types below are based on dbus specification, each field is mapped to a dbus type
|
||||||
|
// see https://dbus.freedesktop.org/doc/dbus-specification.html#basic-types for more details on dbus types
|
||||||
|
// see https://www.freedesktop.org/software/systemd/man/org.freedesktop.resolve1.html on resolve1 input types
|
||||||
|
// systemdDbusDNSInput maps to a (iay) dbus input for SetDNS method
|
||||||
|
type systemdDbusDNSInput struct {
|
||||||
|
Family int32
|
||||||
|
Address []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// systemdDbusLinkDomainsInput maps to a (sb) dbus input for SetDomains method
|
||||||
|
type systemdDbusLinkDomainsInput struct {
|
||||||
|
Domain string
|
||||||
|
MatchOnly bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func newSystemdDbusConfigurator(wgInterface *iface.WGIface) (hostManager, error) {
|
||||||
|
iface, err := net.InterfaceByName(wgInterface.GetName())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
obj, closeConn, err := getDbusObject(systemdResolvedDest, systemdDbusObjectNode)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer closeConn()
|
||||||
|
|
||||||
|
var s string
|
||||||
|
err = obj.Call(systemdDbusGetLinkMethod, dbusDefaultFlag, iface.Index).Store(&s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf("got dbus Link interface: %s from net interface %s and index %d", s, iface.Name, iface.Index)
|
||||||
|
|
||||||
|
return &systemdDbusConfigurator{
|
||||||
|
dbusLinkObject: dbus.ObjectPath(s),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *systemdDbusConfigurator) applyDNSConfig(config hostDNSConfig) error {
|
||||||
|
parsedIP := netip.MustParseAddr(config.serverIP).As4()
|
||||||
|
defaultLinkInput := systemdDbusDNSInput{
|
||||||
|
Family: unix.AF_INET,
|
||||||
|
Address: parsedIP[:],
|
||||||
|
}
|
||||||
|
err := s.callLinkMethod(systemdDbusSetDNSMethodSuffix, []systemdDbusDNSInput{defaultLinkInput})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("setting the interface DNS server %s:%d failed with error: %s", config.serverIP, config.serverPort, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
searchDomains []string
|
||||||
|
matchDomains []string
|
||||||
|
domainsInput []systemdDbusLinkDomainsInput
|
||||||
|
)
|
||||||
|
for _, dConf := range config.domains {
|
||||||
|
domainsInput = append(domainsInput, systemdDbusLinkDomainsInput{
|
||||||
|
Domain: dns.Fqdn(dConf.domain),
|
||||||
|
MatchOnly: dConf.matchOnly,
|
||||||
|
})
|
||||||
|
|
||||||
|
if dConf.matchOnly {
|
||||||
|
matchDomains = append(matchDomains, dConf.domain)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
searchDomains = append(searchDomains, dConf.domain)
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.routeAll {
|
||||||
|
log.Infof("configured %s:%d as main DNS forwarder for this peer", config.serverIP, config.serverPort)
|
||||||
|
err = s.callLinkMethod(systemdDbusSetDefaultRouteMethodSuffix, true)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("setting link as default dns router, failed with error: %s", err)
|
||||||
|
}
|
||||||
|
domainsInput = append(domainsInput, systemdDbusLinkDomainsInput{
|
||||||
|
Domain: nbdns.RootZone,
|
||||||
|
MatchOnly: true,
|
||||||
|
})
|
||||||
|
s.routingAll = true
|
||||||
|
} else if s.routingAll {
|
||||||
|
log.Infof("removing %s:%d as main DNS forwarder for this peer", config.serverIP, config.serverPort)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("adding %d search domains and %d match domains. Search list: %s , Match list: %s", len(searchDomains), len(matchDomains), searchDomains, matchDomains)
|
||||||
|
err = s.setDomainsForInterface(domainsInput)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *systemdDbusConfigurator) setDomainsForInterface(domainsInput []systemdDbusLinkDomainsInput) error {
|
||||||
|
err := s.callLinkMethod(systemdDbusSetDomainsMethodSuffix, domainsInput)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("setting domains configuration failed with error: %s", err)
|
||||||
|
}
|
||||||
|
return s.flushCaches()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *systemdDbusConfigurator) restoreHostDNS() error {
|
||||||
|
log.Infof("reverting link settings and flushing cache")
|
||||||
|
if !isDbusListenerRunning(systemdResolvedDest, s.dbusLinkObject) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
err := s.callLinkMethod(systemdDbusRevertMethodSuffix, nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to revert link configuration, got error: %s", err)
|
||||||
|
}
|
||||||
|
return s.flushCaches()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *systemdDbusConfigurator) flushCaches() error {
|
||||||
|
obj, closeConn, err := getDbusObject(systemdResolvedDest, systemdDbusObjectNode)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("got error while attempting to retrieve the object %s, err: %s", systemdDbusObjectNode, err)
|
||||||
|
}
|
||||||
|
defer closeConn()
|
||||||
|
ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
err = obj.CallWithContext(ctx, systemdDbusFlushCachesMethod, dbusDefaultFlag).Store()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("got error while calling the FlushCaches method with context, err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *systemdDbusConfigurator) callLinkMethod(method string, value any) error {
|
||||||
|
obj, closeConn, err := getDbusObject(systemdResolvedDest, s.dbusLinkObject)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("got error while attempting to retrieve the object, err: %s", err)
|
||||||
|
}
|
||||||
|
defer closeConn()
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
if value != nil {
|
||||||
|
err = obj.CallWithContext(ctx, method, dbusDefaultFlag, value).Store()
|
||||||
|
} else {
|
||||||
|
err = obj.CallWithContext(ctx, method, dbusDefaultFlag).Store()
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("got error while calling command with context, err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
67
client/internal/dns/upstream.go
Normal file
67
client/internal/dns/upstream.go
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const defaultUpstreamTimeout = 15 * time.Second
|
||||||
|
|
||||||
|
type upstreamResolver struct {
|
||||||
|
parentCTX context.Context
|
||||||
|
upstreamClient *dns.Client
|
||||||
|
upstreamServers []string
|
||||||
|
upstreamTimeout time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServeDNS handles a DNS request
|
||||||
|
func (u *upstreamResolver) ServeDNS(w dns.ResponseWriter, r *dns.Msg) {
|
||||||
|
|
||||||
|
log.Debugf("received an upstream question: %#v", r.Question[0])
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-u.parentCTX.Done():
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, upstream := range u.upstreamServers {
|
||||||
|
ctx, cancel := context.WithTimeout(u.parentCTX, u.upstreamTimeout)
|
||||||
|
rm, t, err := u.upstreamClient.ExchangeContext(ctx, r, upstream)
|
||||||
|
|
||||||
|
cancel()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if err == context.DeadlineExceeded || isTimeout(err) {
|
||||||
|
log.Warnf("got an error while connecting to upstream %s, error: %v", upstream, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
log.Errorf("got an error while querying the upstream %s, error: %v", upstream, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Tracef("took %s to query the upstream %s", t, upstream)
|
||||||
|
|
||||||
|
err = w.WriteMsg(rm)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("got an error while writing the upstream resolver response, error: %v", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Errorf("all queries to the upstream nameservers failed with timeout")
|
||||||
|
}
|
||||||
|
|
||||||
|
// isTimeout returns true if the given error is a network timeout error.
|
||||||
|
//
|
||||||
|
// Copied from k8s.io/apimachinery/pkg/util/net.IsTimeout
|
||||||
|
func isTimeout(err error) bool {
|
||||||
|
var neterr net.Error
|
||||||
|
if errors.As(err, &neterr) {
|
||||||
|
return neterr != nil && neterr.Timeout()
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
110
client/internal/dns/upstream_test.go
Normal file
110
client/internal/dns/upstream_test.go
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUpstreamResolver_ServeDNS(t *testing.T) {
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
inputMSG *dns.Msg
|
||||||
|
responseShouldBeNil bool
|
||||||
|
InputServers []string
|
||||||
|
timeout time.Duration
|
||||||
|
cancelCTX bool
|
||||||
|
expectedAnswer string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Should Resolve A Record",
|
||||||
|
inputMSG: new(dns.Msg).SetQuestion("one.one.one.one.", dns.TypeA),
|
||||||
|
InputServers: []string{"8.8.8.8:53", "8.8.4.4:53"},
|
||||||
|
timeout: defaultUpstreamTimeout,
|
||||||
|
expectedAnswer: "1.1.1.1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Should Resolve If First Upstream Times Out",
|
||||||
|
inputMSG: new(dns.Msg).SetQuestion("one.one.one.one.", dns.TypeA),
|
||||||
|
InputServers: []string{"8.0.0.0:53", "8.8.4.4:53"},
|
||||||
|
timeout: 2 * time.Second,
|
||||||
|
expectedAnswer: "1.1.1.1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Should Not Resolve If Can't Connect To Both Servers",
|
||||||
|
inputMSG: new(dns.Msg).SetQuestion("one.one.one.one.", dns.TypeA),
|
||||||
|
InputServers: []string{"8.0.0.0:53", "8.0.0.1:53"},
|
||||||
|
timeout: 200 * time.Millisecond,
|
||||||
|
responseShouldBeNil: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Should Not Resolve If Parent Context Is Canceled",
|
||||||
|
inputMSG: new(dns.Msg).SetQuestion("one.one.one.one.", dns.TypeA),
|
||||||
|
InputServers: []string{"8.0.0.0:53", "8.8.4.4:53"},
|
||||||
|
cancelCTX: true,
|
||||||
|
timeout: defaultUpstreamTimeout,
|
||||||
|
responseShouldBeNil: true,
|
||||||
|
},
|
||||||
|
//{
|
||||||
|
// name: "Should Resolve CNAME Record",
|
||||||
|
// inputMSG: new(dns.Msg).SetQuestion("one.one.one.one", dns.TypeCNAME),
|
||||||
|
//},
|
||||||
|
//{
|
||||||
|
// name: "Should Not Write When Not Found A Record",
|
||||||
|
// inputMSG: new(dns.Msg).SetQuestion("not.found.com", dns.TypeA),
|
||||||
|
// responseShouldBeNil: true,
|
||||||
|
//},
|
||||||
|
}
|
||||||
|
// should resolve if first upstream times out
|
||||||
|
// should not write when both fails
|
||||||
|
// should not resolve if parent context is canceled
|
||||||
|
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
|
ctx, cancel := context.WithCancel(context.TODO())
|
||||||
|
resolver := &upstreamResolver{
|
||||||
|
parentCTX: ctx,
|
||||||
|
upstreamClient: &dns.Client{},
|
||||||
|
upstreamServers: testCase.InputServers,
|
||||||
|
upstreamTimeout: testCase.timeout,
|
||||||
|
}
|
||||||
|
if testCase.cancelCTX {
|
||||||
|
cancel()
|
||||||
|
} else {
|
||||||
|
defer cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
var responseMSG *dns.Msg
|
||||||
|
responseWriter := &mockResponseWriter{
|
||||||
|
WriteMsgFunc: func(m *dns.Msg) error {
|
||||||
|
responseMSG = m
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
resolver.ServeDNS(responseWriter, testCase.inputMSG)
|
||||||
|
|
||||||
|
if responseMSG == nil {
|
||||||
|
if testCase.responseShouldBeNil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.Fatalf("should write a response message")
|
||||||
|
}
|
||||||
|
|
||||||
|
foundAnswer := false
|
||||||
|
for _, answer := range responseMSG.Answer {
|
||||||
|
if strings.Contains(answer.String(), testCase.expectedAnswer) {
|
||||||
|
foundAnswer = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !foundAnswer {
|
||||||
|
t.Errorf("couldn't find the required answer, %s, in the dns response", testCase.expectedAnswer)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,18 +3,22 @@ package internal
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/netbirdio/netbird/client/internal/routemanager"
|
|
||||||
nbssh "github.com/netbirdio/netbird/client/ssh"
|
|
||||||
nbstatus "github.com/netbirdio/netbird/client/status"
|
|
||||||
"github.com/netbirdio/netbird/route"
|
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
|
"net/netip"
|
||||||
"reflect"
|
"reflect"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/client/internal/dns"
|
||||||
|
"github.com/netbirdio/netbird/client/internal/routemanager"
|
||||||
|
nbssh "github.com/netbirdio/netbird/client/ssh"
|
||||||
|
nbstatus "github.com/netbirdio/netbird/client/status"
|
||||||
|
nbdns "github.com/netbirdio/netbird/dns"
|
||||||
|
"github.com/netbirdio/netbird/route"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/internal/peer"
|
"github.com/netbirdio/netbird/client/internal/peer"
|
||||||
"github.com/netbirdio/netbird/client/internal/proxy"
|
"github.com/netbirdio/netbird/client/internal/proxy"
|
||||||
"github.com/netbirdio/netbird/iface"
|
"github.com/netbirdio/netbird/iface"
|
||||||
@@ -51,7 +55,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 []string
|
IFaceBlackList []string
|
||||||
|
DisableIPv6Discovery bool
|
||||||
|
|
||||||
PreSharedKey *wgtypes.Key
|
PreSharedKey *wgtypes.Key
|
||||||
|
|
||||||
@@ -63,6 +68,8 @@ type EngineConfig struct {
|
|||||||
|
|
||||||
// SSHKey is a private SSH key in a PEM format
|
// SSHKey is a private SSH key in a PEM format
|
||||||
SSHKey []byte
|
SSHKey []byte
|
||||||
|
|
||||||
|
NATExternalIPs []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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.
|
||||||
@@ -103,6 +110,8 @@ type Engine struct {
|
|||||||
statusRecorder *nbstatus.Status
|
statusRecorder *nbstatus.Status
|
||||||
|
|
||||||
routeManager routemanager.Manager
|
routeManager routemanager.Manager
|
||||||
|
|
||||||
|
dnsServer dns.Server
|
||||||
}
|
}
|
||||||
|
|
||||||
// Peer is an instance of the Connection Peer
|
// Peer is an instance of the Connection Peer
|
||||||
@@ -190,6 +199,10 @@ func (e *Engine) Stop() error {
|
|||||||
e.routeManager.Stop()
|
e.routeManager.Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if e.dnsServer != nil {
|
||||||
|
e.dnsServer.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
log.Infof("stopped Netbird Engine")
|
log.Infof("stopped Netbird Engine")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -213,13 +226,18 @@ func (e *Engine) Start() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
e.udpMuxConn, err = net.ListenUDP("udp4", &net.UDPAddr{Port: e.config.UDPMuxPort})
|
networkName := "udp"
|
||||||
|
if e.config.DisableIPv6Discovery {
|
||||||
|
networkName = "udp4"
|
||||||
|
}
|
||||||
|
|
||||||
|
e.udpMuxConn, err = net.ListenUDP(networkName, &net.UDPAddr{Port: e.config.UDPMuxPort})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed listening on UDP port %d: [%s]", e.config.UDPMuxPort, err.Error())
|
log.Errorf("failed listening on UDP port %d: [%s]", e.config.UDPMuxPort, err.Error())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
e.udpMuxConnSrflx, err = net.ListenUDP("udp4", &net.UDPAddr{Port: e.config.UDPMuxSrflxPort})
|
e.udpMuxConnSrflx, err = net.ListenUDP(networkName, &net.UDPAddr{Port: e.config.UDPMuxSrflxPort})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed listening on UDP port %d: [%s]", e.config.UDPMuxSrflxPort, err.Error())
|
log.Errorf("failed listening on UDP port %d: [%s]", e.config.UDPMuxSrflxPort, err.Error())
|
||||||
return err
|
return err
|
||||||
@@ -242,6 +260,14 @@ func (e *Engine) Start() error {
|
|||||||
|
|
||||||
e.routeManager = routemanager.NewManager(e.ctx, e.config.WgPrivateKey.PublicKey().String(), e.wgInterface, e.statusRecorder)
|
e.routeManager = routemanager.NewManager(e.ctx, e.config.WgPrivateKey.PublicKey().String(), e.wgInterface, e.statusRecorder)
|
||||||
|
|
||||||
|
if e.dnsServer == nil {
|
||||||
|
dnsServer, err := dns.NewDefaultServer(e.ctx, e.wgInterface)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
e.dnsServer = dnsServer
|
||||||
|
}
|
||||||
|
|
||||||
e.receiveSignalEvents()
|
e.receiveSignalEvents()
|
||||||
e.receiveManagementEvents()
|
e.receiveManagementEvents()
|
||||||
|
|
||||||
@@ -638,6 +664,15 @@ func (e *Engine) updateNetworkMap(networkMap *mgmProto.NetworkMap) error {
|
|||||||
log.Errorf("failed to update routes, err: %v", err)
|
log.Errorf("failed to update routes, err: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protoDNSConfig := networkMap.GetDNSConfig()
|
||||||
|
if protoDNSConfig == nil {
|
||||||
|
protoDNSConfig = &mgmProto.DNSConfig{}
|
||||||
|
}
|
||||||
|
err = e.dnsServer.UpdateDNSServer(serial, toDNSConfig(protoDNSConfig))
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to update dns server, err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
e.networkSerial = serial
|
e.networkSerial = serial
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -660,6 +695,48 @@ func toRoutes(protoRoutes []*mgmProto.Route) []*route.Route {
|
|||||||
return routes
|
return routes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func toDNSConfig(protoDNSConfig *mgmProto.DNSConfig) nbdns.Config {
|
||||||
|
dnsUpdate := nbdns.Config{
|
||||||
|
ServiceEnable: protoDNSConfig.GetServiceEnable(),
|
||||||
|
CustomZones: make([]nbdns.CustomZone, 0),
|
||||||
|
NameServerGroups: make([]*nbdns.NameServerGroup, 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, zone := range protoDNSConfig.GetCustomZones() {
|
||||||
|
dnsZone := nbdns.CustomZone{
|
||||||
|
Domain: zone.GetDomain(),
|
||||||
|
}
|
||||||
|
for _, record := range zone.Records {
|
||||||
|
dnsRecord := nbdns.SimpleRecord{
|
||||||
|
Name: record.GetName(),
|
||||||
|
Type: int(record.GetType()),
|
||||||
|
Class: record.GetClass(),
|
||||||
|
TTL: int(record.GetTTL()),
|
||||||
|
RData: record.GetRData(),
|
||||||
|
}
|
||||||
|
dnsZone.Records = append(dnsZone.Records, dnsRecord)
|
||||||
|
}
|
||||||
|
dnsUpdate.CustomZones = append(dnsUpdate.CustomZones, dnsZone)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, nsGroup := range protoDNSConfig.GetNameServerGroups() {
|
||||||
|
dnsNSGroup := &nbdns.NameServerGroup{
|
||||||
|
Primary: nsGroup.GetPrimary(),
|
||||||
|
Domains: nsGroup.GetDomains(),
|
||||||
|
}
|
||||||
|
for _, ns := range nsGroup.GetNameServers() {
|
||||||
|
dnsNS := nbdns.NameServer{
|
||||||
|
IP: netip.MustParseAddr(ns.GetIP()),
|
||||||
|
NSType: nbdns.NameServerType(ns.GetNSType()),
|
||||||
|
Port: int(ns.GetPort()),
|
||||||
|
}
|
||||||
|
dnsNSGroup.NameServers = append(dnsNSGroup.NameServers, dnsNS)
|
||||||
|
}
|
||||||
|
dnsUpdate.NameServerGroups = append(dnsUpdate.NameServerGroups, dnsNSGroup)
|
||||||
|
}
|
||||||
|
return dnsUpdate
|
||||||
|
}
|
||||||
|
|
||||||
// addNewPeers adds peers that were not know before but arrived from the Management service with the update
|
// addNewPeers adds peers that were not know before but arrived from the Management service with the update
|
||||||
func (e *Engine) addNewPeers(peersUpdate []*mgmProto.RemotePeerConfig) error {
|
func (e *Engine) addNewPeers(peersUpdate []*mgmProto.RemotePeerConfig) error {
|
||||||
for _, p := range peersUpdate {
|
for _, p := range peersUpdate {
|
||||||
@@ -755,15 +832,17 @@ func (e Engine) createPeerConn(pubKey string, allowedIPs string) (*peer.Conn, er
|
|||||||
// randomize connection timeout
|
// randomize connection timeout
|
||||||
timeout := time.Duration(rand.Intn(PeerConnectionTimeoutMax-PeerConnectionTimeoutMin)+PeerConnectionTimeoutMin) * time.Millisecond
|
timeout := time.Duration(rand.Intn(PeerConnectionTimeoutMax-PeerConnectionTimeoutMin)+PeerConnectionTimeoutMin) * time.Millisecond
|
||||||
config := peer.ConnConfig{
|
config := peer.ConnConfig{
|
||||||
Key: pubKey,
|
Key: pubKey,
|
||||||
LocalKey: e.config.WgPrivateKey.PublicKey().String(),
|
LocalKey: e.config.WgPrivateKey.PublicKey().String(),
|
||||||
StunTurn: stunTurn,
|
StunTurn: stunTurn,
|
||||||
InterfaceBlackList: e.config.IFaceBlackList,
|
InterfaceBlackList: e.config.IFaceBlackList,
|
||||||
Timeout: timeout,
|
DisableIPv6Discovery: e.config.DisableIPv6Discovery,
|
||||||
UDPMux: e.udpMux,
|
Timeout: timeout,
|
||||||
UDPMuxSrflx: e.udpMuxSrflx,
|
UDPMux: e.udpMux,
|
||||||
ProxyConfig: proxyConfig,
|
UDPMuxSrflx: e.udpMuxSrflx,
|
||||||
LocalWgPort: e.config.WgPort,
|
ProxyConfig: proxyConfig,
|
||||||
|
LocalWgPort: e.config.WgPort,
|
||||||
|
NATExternalIPs: e.parseNATExternalIPMappings(),
|
||||||
}
|
}
|
||||||
|
|
||||||
peerConn, err := peer.NewConn(config, e.statusRecorder)
|
peerConn, err := peer.NewConn(config, e.statusRecorder)
|
||||||
@@ -857,3 +936,77 @@ func (e *Engine) receiveSignalEvents() {
|
|||||||
|
|
||||||
e.signal.WaitStreamConnected()
|
e.signal.WaitStreamConnected()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e* Engine) parseNATExternalIPMappings() []string {
|
||||||
|
var mappedIPs []string
|
||||||
|
var ignoredIFaces = make(map[string]interface{})
|
||||||
|
for _, iFace := range(e.config.IFaceBlackList) {
|
||||||
|
ignoredIFaces[iFace] = nil
|
||||||
|
}
|
||||||
|
for _, mapping := range e.config.NATExternalIPs {
|
||||||
|
var external, internal string
|
||||||
|
var externalIP, internalIP net.IP
|
||||||
|
var err error
|
||||||
|
split := strings.Split(mapping, "/")
|
||||||
|
if len(split) > 2 {
|
||||||
|
log.Warnf("ignoring invalid external mapping '%s', too many delimiters", mapping)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if len(split) > 1 {
|
||||||
|
internal = split[1]
|
||||||
|
internalIP = net.ParseIP(internal)
|
||||||
|
if internalIP == nil {
|
||||||
|
// not a properly formatted IP address, maybe it's interface name?
|
||||||
|
if _, present := ignoredIFaces[internal]; present {
|
||||||
|
log.Warnf("internal interface '%s' in blacklist, ignoring external mapping '%s'", internal, mapping)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
internalIP, err = findIPFromInterfaceName(internal)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("error finding interface IP for interface '%s', ignoring external mapping '%s': %v", internal, mapping, err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
external = split[0]
|
||||||
|
externalIP = net.ParseIP(external)
|
||||||
|
if externalIP == nil {
|
||||||
|
log.Warnf("invalid external IP, ignoring external IP mapping '%s'", mapping)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if externalIP != nil {
|
||||||
|
mappedIP := externalIP.String()
|
||||||
|
if internalIP != nil {
|
||||||
|
mappedIP = mappedIP + "/" + internalIP.String()
|
||||||
|
}
|
||||||
|
mappedIPs = append(mappedIPs, mappedIP)
|
||||||
|
log.Infof("parsed external IP mapping of '%s' as '%s'", mapping, mappedIP)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(mappedIPs) != len(e.config.NATExternalIPs) {
|
||||||
|
log.Warnf("one or more external IP mappings failed to parse, ignoring all mappings")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return mappedIPs
|
||||||
|
}
|
||||||
|
|
||||||
|
func findIPFromInterfaceName(ifaceName string) (net.IP, error) {
|
||||||
|
iface, err := net.InterfaceByName(ifaceName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return findIPFromInterface(iface)
|
||||||
|
}
|
||||||
|
|
||||||
|
func findIPFromInterface(iface *net.Interface) (net.IP, error) {
|
||||||
|
ifaceAddrs, err := iface.Addrs()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, addr := range ifaceAddrs {
|
||||||
|
if ipv4Addr := addr.(*net.IPNet).IP.To4(); ipv4Addr != nil {
|
||||||
|
return ipv4Addr, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("interface %s don't have an ipv4 address", iface.Name)
|
||||||
|
}
|
||||||
@@ -3,9 +3,11 @@ package internal
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/netbirdio/netbird/client/internal/dns"
|
||||||
"github.com/netbirdio/netbird/client/internal/routemanager"
|
"github.com/netbirdio/netbird/client/internal/routemanager"
|
||||||
"github.com/netbirdio/netbird/client/ssh"
|
"github.com/netbirdio/netbird/client/ssh"
|
||||||
nbstatus "github.com/netbirdio/netbird/client/status"
|
nbstatus "github.com/netbirdio/netbird/client/status"
|
||||||
|
nbdns "github.com/netbirdio/netbird/dns"
|
||||||
"github.com/netbirdio/netbird/iface"
|
"github.com/netbirdio/netbird/iface"
|
||||||
"github.com/netbirdio/netbird/route"
|
"github.com/netbirdio/netbird/route"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@@ -200,6 +202,9 @@ func TestEngine_UpdateNetworkMap(t *testing.T) {
|
|||||||
}, nbstatus.NewRecorder())
|
}, nbstatus.NewRecorder())
|
||||||
engine.wgInterface, err = iface.NewWGIFace("utun102", "100.64.0.1/24", iface.DefaultMTU)
|
engine.wgInterface, err = iface.NewWGIFace("utun102", "100.64.0.1/24", iface.DefaultMTU)
|
||||||
engine.routeManager = routemanager.NewManager(ctx, key.PublicKey().String(), engine.wgInterface, engine.statusRecorder)
|
engine.routeManager = routemanager.NewManager(ctx, key.PublicKey().String(), engine.wgInterface, engine.statusRecorder)
|
||||||
|
engine.dnsServer = &dns.MockServer{
|
||||||
|
UpdateDNSServerFunc: func(serial uint64, update nbdns.Config) error { return nil },
|
||||||
|
}
|
||||||
|
|
||||||
type testCase struct {
|
type testCase struct {
|
||||||
name string
|
name string
|
||||||
@@ -440,7 +445,7 @@ func TestEngine_UpdateNetworkMapWithRoutes(t *testing.T) {
|
|||||||
expectedSerial uint64
|
expectedSerial uint64
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "Routes Update Should Be Passed To Manager",
|
name: "Routes Config Should Be Passed To Manager",
|
||||||
networkMap: &mgmtProto.NetworkMap{
|
networkMap: &mgmtProto.NetworkMap{
|
||||||
Serial: 1,
|
Serial: 1,
|
||||||
PeerConfig: nil,
|
PeerConfig: nil,
|
||||||
@@ -486,7 +491,7 @@ func TestEngine_UpdateNetworkMapWithRoutes(t *testing.T) {
|
|||||||
expectedSerial: 1,
|
expectedSerial: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Empty Routes Update Should Be Passed",
|
name: "Empty Routes Config Should Be Passed",
|
||||||
networkMap: &mgmtProto.NetworkMap{
|
networkMap: &mgmtProto.NetworkMap{
|
||||||
Serial: 1,
|
Serial: 1,
|
||||||
PeerConfig: nil,
|
PeerConfig: nil,
|
||||||
@@ -549,6 +554,7 @@ func TestEngine_UpdateNetworkMapWithRoutes(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
engine.routeManager = mockRouteManager
|
engine.routeManager = mockRouteManager
|
||||||
|
engine.dnsServer = &dns.MockServer{}
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
exitErr := engine.Stop()
|
exitErr := engine.Stop()
|
||||||
@@ -566,6 +572,183 @@ func TestEngine_UpdateNetworkMapWithRoutes(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestEngine_UpdateNetworkMapWithDNSUpdate(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
inputErr error
|
||||||
|
networkMap *mgmtProto.NetworkMap
|
||||||
|
expectedZonesLen int
|
||||||
|
expectedZones []nbdns.CustomZone
|
||||||
|
expectedNSGroupsLen int
|
||||||
|
expectedNSGroups []*nbdns.NameServerGroup
|
||||||
|
expectedSerial uint64
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "DNS Config Should Be Passed To DNS Server",
|
||||||
|
networkMap: &mgmtProto.NetworkMap{
|
||||||
|
Serial: 1,
|
||||||
|
PeerConfig: nil,
|
||||||
|
RemotePeersIsEmpty: false,
|
||||||
|
Routes: nil,
|
||||||
|
DNSConfig: &mgmtProto.DNSConfig{
|
||||||
|
ServiceEnable: true,
|
||||||
|
CustomZones: []*mgmtProto.CustomZone{
|
||||||
|
{
|
||||||
|
Domain: "netbird.cloud.",
|
||||||
|
Records: []*mgmtProto.SimpleRecord{
|
||||||
|
{
|
||||||
|
Name: "peer-a.netbird.cloud.",
|
||||||
|
Type: 1,
|
||||||
|
Class: nbdns.DefaultClass,
|
||||||
|
TTL: 300,
|
||||||
|
RData: "100.64.0.1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
NameServerGroups: []*mgmtProto.NameServerGroup{
|
||||||
|
{
|
||||||
|
Primary: true,
|
||||||
|
NameServers: []*mgmtProto.NameServer{
|
||||||
|
{
|
||||||
|
IP: "8.8.8.8",
|
||||||
|
NSType: 1,
|
||||||
|
Port: 53,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedZonesLen: 1,
|
||||||
|
expectedZones: []nbdns.CustomZone{
|
||||||
|
{
|
||||||
|
Domain: "netbird.cloud.",
|
||||||
|
Records: []nbdns.SimpleRecord{
|
||||||
|
{
|
||||||
|
Name: "peer-a.netbird.cloud.",
|
||||||
|
Type: 1,
|
||||||
|
Class: nbdns.DefaultClass,
|
||||||
|
TTL: 300,
|
||||||
|
RData: "100.64.0.1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedNSGroupsLen: 1,
|
||||||
|
expectedNSGroups: []*nbdns.NameServerGroup{
|
||||||
|
{
|
||||||
|
Primary: true,
|
||||||
|
NameServers: []nbdns.NameServer{
|
||||||
|
{
|
||||||
|
IP: netip.MustParseAddr("8.8.8.8"),
|
||||||
|
NSType: 1,
|
||||||
|
Port: 53,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedSerial: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Empty DNS Config Should Be OK",
|
||||||
|
networkMap: &mgmtProto.NetworkMap{
|
||||||
|
Serial: 1,
|
||||||
|
PeerConfig: nil,
|
||||||
|
RemotePeersIsEmpty: false,
|
||||||
|
Routes: nil,
|
||||||
|
DNSConfig: nil,
|
||||||
|
},
|
||||||
|
expectedZonesLen: 0,
|
||||||
|
expectedZones: []nbdns.CustomZone{},
|
||||||
|
expectedNSGroupsLen: 0,
|
||||||
|
expectedNSGroups: []*nbdns.NameServerGroup{},
|
||||||
|
expectedSerial: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Error Shouldn't Break Engine",
|
||||||
|
inputErr: fmt.Errorf("mocking error"),
|
||||||
|
networkMap: &mgmtProto.NetworkMap{
|
||||||
|
Serial: 1,
|
||||||
|
PeerConfig: nil,
|
||||||
|
RemotePeersIsEmpty: false,
|
||||||
|
Routes: nil,
|
||||||
|
},
|
||||||
|
expectedZonesLen: 0,
|
||||||
|
expectedZones: []nbdns.CustomZone{},
|
||||||
|
expectedNSGroupsLen: 0,
|
||||||
|
expectedNSGroups: []*nbdns.NameServerGroup{},
|
||||||
|
expectedSerial: 1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for n, testCase := range testCases {
|
||||||
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
|
// test setup
|
||||||
|
key, err := wgtypes.GeneratePrivateKey()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
wgIfaceName := fmt.Sprintf("utun%d", 104+n)
|
||||||
|
wgAddr := fmt.Sprintf("100.66.%d.1/24", n)
|
||||||
|
|
||||||
|
engine := NewEngine(ctx, cancel, &signal.MockClient{}, &mgmt.MockClient{}, &EngineConfig{
|
||||||
|
WgIfaceName: wgIfaceName,
|
||||||
|
WgAddr: wgAddr,
|
||||||
|
WgPrivateKey: key,
|
||||||
|
WgPort: 33100,
|
||||||
|
}, nbstatus.NewRecorder())
|
||||||
|
engine.wgInterface, err = iface.NewWGIFace(wgIfaceName, wgAddr, iface.DefaultMTU)
|
||||||
|
assert.NoError(t, err, "shouldn't return error")
|
||||||
|
|
||||||
|
mockRouteManager := &routemanager.MockManager{
|
||||||
|
UpdateRoutesFunc: func(updateSerial uint64, newRoutes []*route.Route) error {
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
engine.routeManager = mockRouteManager
|
||||||
|
|
||||||
|
input := struct {
|
||||||
|
inputSerial uint64
|
||||||
|
inputNSGroups []*nbdns.NameServerGroup
|
||||||
|
inputZones []nbdns.CustomZone
|
||||||
|
}{}
|
||||||
|
|
||||||
|
mockDNSServer := &dns.MockServer{
|
||||||
|
UpdateDNSServerFunc: func(serial uint64, update nbdns.Config) error {
|
||||||
|
input.inputSerial = serial
|
||||||
|
input.inputZones = update.CustomZones
|
||||||
|
input.inputNSGroups = update.NameServerGroups
|
||||||
|
return testCase.inputErr
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
engine.dnsServer = mockDNSServer
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
exitErr := engine.Stop()
|
||||||
|
if exitErr != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
err = engine.updateNetworkMap(testCase.networkMap)
|
||||||
|
assert.NoError(t, err, "shouldn't return error")
|
||||||
|
assert.Equal(t, testCase.expectedSerial, input.inputSerial, "serial should match")
|
||||||
|
assert.Len(t, input.inputNSGroups, testCase.expectedZonesLen, "zones len should match")
|
||||||
|
assert.Equal(t, testCase.expectedZones, input.inputZones, "custom zones should match")
|
||||||
|
assert.Len(t, input.inputNSGroups, testCase.expectedNSGroupsLen, "ns groups len should match")
|
||||||
|
assert.Equal(t, testCase.expectedNSGroups, input.inputNSGroups, "ns groups should match")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestEngine_MultiplePeers(t *testing.T) {
|
func TestEngine_MultiplePeers(t *testing.T) {
|
||||||
// log.SetLevel(log.DebugLevel)
|
// log.SetLevel(log.DebugLevel)
|
||||||
|
|
||||||
@@ -618,6 +801,7 @@ func TestEngine_MultiplePeers(t *testing.T) {
|
|||||||
t.Errorf("unable to create the engine for peer %d with error %v", j, err)
|
t.Errorf("unable to create the engine for peer %d with error %v", j, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
engine.dnsServer = &dns.MockServer{}
|
||||||
mu.Lock()
|
mu.Lock()
|
||||||
defer mu.Unlock()
|
defer mu.Unlock()
|
||||||
err = engine.Start()
|
err = engine.Start()
|
||||||
@@ -756,17 +940,17 @@ func startManagement(port int, dataDir string) (*grpc.Server, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
s := grpc.NewServer(grpc.KeepaliveEnforcementPolicy(kaep), grpc.KeepaliveParams(kasp))
|
s := grpc.NewServer(grpc.KeepaliveEnforcementPolicy(kaep), grpc.KeepaliveParams(kasp))
|
||||||
store, err := server.NewStore(config.Datadir)
|
store, err := server.NewFileStore(config.Datadir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed creating a store: %s: %v", config.Datadir, err)
|
log.Fatalf("failed creating a store: %s: %v", config.Datadir, err)
|
||||||
}
|
}
|
||||||
peersUpdateManager := server.NewPeersUpdateManager()
|
peersUpdateManager := server.NewPeersUpdateManager()
|
||||||
accountManager, err := server.BuildManager(store, peersUpdateManager, nil)
|
accountManager, err := server.BuildManager(store, peersUpdateManager, nil, "", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
turnManager := server.NewTimeBasedAuthSecretsManager(peersUpdateManager, config.TURNConfig)
|
turnManager := server.NewTimeBasedAuthSecretsManager(peersUpdateManager, config.TURNConfig)
|
||||||
mgmtServer, err := server.NewServer(config, accountManager, peersUpdateManager, turnManager)
|
mgmtServer, err := server.NewServer(config, accountManager, peersUpdateManager, turnManager, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,10 @@ func Login(ctx context.Context, config *Config, setupKey string, jwtToken string
|
|||||||
defer func() {
|
defer func() {
|
||||||
err = mgmClient.Close()
|
err = mgmClient.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("failed to close the Management service client %v", err)
|
cStatus, ok := status.FromError(err)
|
||||||
|
if !ok || ok && cStatus.Code() != codes.Canceled {
|
||||||
|
log.Warnf("failed to close the Management service client, err: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|||||||
@@ -2,18 +2,18 @@ package peer
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
nbStatus "github.com/netbirdio/netbird/client/status"
|
|
||||||
"github.com/netbirdio/netbird/client/system"
|
|
||||||
"github.com/netbirdio/netbird/iface"
|
|
||||||
"golang.zx2c4.com/wireguard/wgctrl"
|
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/internal/proxy"
|
"github.com/netbirdio/netbird/client/internal/proxy"
|
||||||
|
nbStatus "github.com/netbirdio/netbird/client/status"
|
||||||
|
"github.com/netbirdio/netbird/client/system"
|
||||||
|
"github.com/netbirdio/netbird/iface"
|
||||||
"github.com/pion/ice/v2"
|
"github.com/pion/ice/v2"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
"golang.zx2c4.com/wireguard/wgctrl"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ConnConfig is a peer Connection configuration
|
// ConnConfig is a peer Connection configuration
|
||||||
@@ -29,7 +29,8 @@ type ConnConfig struct {
|
|||||||
|
|
||||||
// InterfaceBlackList is a list of machine interfaces that should be filtered out by ICE Candidate gathering
|
// InterfaceBlackList is a list of machine interfaces that should be filtered out by ICE Candidate gathering
|
||||||
// (e.g. if eth0 is in the list, host candidate of this interface won't be used)
|
// (e.g. if eth0 is in the list, host candidate of this interface won't be used)
|
||||||
InterfaceBlackList []string
|
InterfaceBlackList []string
|
||||||
|
DisableIPv6Discovery bool
|
||||||
|
|
||||||
Timeout time.Duration
|
Timeout time.Duration
|
||||||
|
|
||||||
@@ -39,6 +40,8 @@ type ConnConfig struct {
|
|||||||
UDPMuxSrflx ice.UniversalUDPMux
|
UDPMuxSrflx ice.UniversalUDPMux
|
||||||
|
|
||||||
LocalWgPort int
|
LocalWgPort int
|
||||||
|
|
||||||
|
NATExternalIPs []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// OfferAnswer represents a session establishment offer or answer
|
// OfferAnswer represents a session establishment offer or answer
|
||||||
@@ -143,16 +146,24 @@ func (conn *Conn) reCreateAgent() error {
|
|||||||
|
|
||||||
failedTimeout := 6 * time.Second
|
failedTimeout := 6 * time.Second
|
||||||
var err error
|
var err error
|
||||||
conn.agent, err = ice.NewAgent(&ice.AgentConfig{
|
agentConfig := &ice.AgentConfig{
|
||||||
MulticastDNSMode: ice.MulticastDNSModeDisabled,
|
MulticastDNSMode: ice.MulticastDNSModeDisabled,
|
||||||
NetworkTypes: []ice.NetworkType{ice.NetworkTypeUDP4},
|
NetworkTypes: []ice.NetworkType{ice.NetworkTypeUDP4, ice.NetworkTypeUDP6},
|
||||||
Urls: conn.config.StunTurn,
|
Urls: conn.config.StunTurn,
|
||||||
CandidateTypes: []ice.CandidateType{ice.CandidateTypeHost, ice.CandidateTypeServerReflexive, ice.CandidateTypeRelay},
|
CandidateTypes: []ice.CandidateType{ice.CandidateTypeHost, ice.CandidateTypeServerReflexive, ice.CandidateTypeRelay},
|
||||||
FailedTimeout: &failedTimeout,
|
FailedTimeout: &failedTimeout,
|
||||||
InterfaceFilter: interfaceFilter(conn.config.InterfaceBlackList),
|
InterfaceFilter: interfaceFilter(conn.config.InterfaceBlackList),
|
||||||
UDPMux: conn.config.UDPMux,
|
UDPMux: conn.config.UDPMux,
|
||||||
UDPMuxSrflx: conn.config.UDPMuxSrflx,
|
UDPMuxSrflx: conn.config.UDPMuxSrflx,
|
||||||
})
|
NAT1To1IPs: conn.config.NATExternalIPs,
|
||||||
|
}
|
||||||
|
|
||||||
|
if conn.config.DisableIPv6Discovery {
|
||||||
|
agentConfig.NetworkTypes = []ice.NetworkType{ice.NetworkTypeUDP4}
|
||||||
|
}
|
||||||
|
|
||||||
|
conn.agent, err = ice.NewAgent(agentConfig)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -284,7 +295,7 @@ func (conn *Conn) Open() error {
|
|||||||
host, _, _ := net.SplitHostPort(remoteConn.LocalAddr().String())
|
host, _, _ := net.SplitHostPort(remoteConn.LocalAddr().String())
|
||||||
rhost, _, _ := net.SplitHostPort(remoteConn.RemoteAddr().String())
|
rhost, _, _ := net.SplitHostPort(remoteConn.RemoteAddr().String())
|
||||||
// direct Wireguard connection
|
// direct Wireguard connection
|
||||||
log.Infof("directly connected to peer %s [laddr <-> raddr] [%s:%d <-> %s:%d]", conn.config.Key, host, iface.DefaultWgPort, rhost, iface.DefaultWgPort)
|
log.Infof("directly connected to peer %s [laddr <-> raddr] [%s:%d <-> %s:%d]", conn.config.Key, host, conn.config.LocalWgPort, rhost, remoteWgPort)
|
||||||
} else {
|
} else {
|
||||||
log.Infof("connected to peer %s [laddr <-> raddr] [%s <-> %s]", conn.config.Key, remoteConn.LocalAddr().String(), remoteConn.RemoteAddr().String())
|
log.Infof("connected to peer %s [laddr <-> raddr] [%s <-> %s]", conn.config.Key, remoteConn.LocalAddr().String(), remoteConn.RemoteAddr().String())
|
||||||
}
|
}
|
||||||
@@ -448,6 +459,7 @@ func (conn *Conn) SetSignalCandidate(handler func(candidate ice.Candidate) error
|
|||||||
// and then signals them to the remote peer
|
// and then signals them to the remote peer
|
||||||
func (conn *Conn) onICECandidate(candidate ice.Candidate) {
|
func (conn *Conn) onICECandidate(candidate ice.Candidate) {
|
||||||
if candidate != nil {
|
if candidate != nil {
|
||||||
|
// TODO: reported port is incorrect for CandidateTypeHost, makes understanding ICE use via logs confusing as port is ignored
|
||||||
log.Debugf("discovered local candidate %s", candidate.String())
|
log.Debugf("discovered local candidate %s", candidate.String())
|
||||||
go func() {
|
go func() {
|
||||||
err := conn.signalCandidate(candidate)
|
err := conn.signalCandidate(candidate)
|
||||||
|
|||||||
@@ -207,7 +207,7 @@ func (c *clientNetwork) recalculateRouteAndUpdatePeerAndSystem() error {
|
|||||||
err = addToRouteTableIfNoExists(c.network, c.wgInterface.GetAddress().IP.String())
|
err = addToRouteTableIfNoExists(c.network, c.wgInterface.GetAddress().IP.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("route %s couldn't be added for peer %s, err: %v",
|
return fmt.Errorf("route %s couldn't be added for peer %s, err: %v",
|
||||||
c.chosenRoute.Network.String(), c.wgInterface.GetAddress().IP.String(), err)
|
c.network.String(), c.wgInterface.GetAddress().IP.String(), err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,14 +9,16 @@ import (
|
|||||||
import "github.com/google/nftables"
|
import "github.com/google/nftables"
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ipv6Forwarding = "netbird-rt-ipv6-forwarding"
|
ipv6Forwarding = "netbird-rt-ipv6-forwarding"
|
||||||
ipv4Forwarding = "netbird-rt-ipv4-forwarding"
|
ipv4Forwarding = "netbird-rt-ipv4-forwarding"
|
||||||
ipv6Nat = "netbird-rt-ipv6-nat"
|
ipv6Nat = "netbird-rt-ipv6-nat"
|
||||||
ipv4Nat = "netbird-rt-ipv4-nat"
|
ipv4Nat = "netbird-rt-ipv4-nat"
|
||||||
natFormat = "netbird-nat-%s"
|
natFormat = "netbird-nat-%s"
|
||||||
forwardingFormat = "netbird-fwd-%s"
|
forwardingFormat = "netbird-fwd-%s"
|
||||||
ipv6 = "ipv6"
|
inNatFormat = "netbird-nat-in-%s"
|
||||||
ipv4 = "ipv4"
|
inForwardingFormat = "netbird-fwd-in-%s"
|
||||||
|
ipv6 = "ipv6"
|
||||||
|
ipv4 = "ipv4"
|
||||||
)
|
)
|
||||||
|
|
||||||
func genKey(format string, input string) string {
|
func genKey(format string, input string) string {
|
||||||
@@ -53,3 +55,13 @@ func NewFirewall(parentCTX context.Context) firewallManager {
|
|||||||
|
|
||||||
return manager
|
return manager
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getInPair(pair routerPair) routerPair {
|
||||||
|
return routerPair{
|
||||||
|
ID: pair.ID,
|
||||||
|
// invert source/destination
|
||||||
|
source: pair.destination,
|
||||||
|
destination: pair.source,
|
||||||
|
masquerade: pair.masquerade,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -311,7 +311,37 @@ func (i *iptablesManager) InsertRoutingRules(pair routerPair) error {
|
|||||||
i.mux.Lock()
|
i.mux.Lock()
|
||||||
defer i.mux.Unlock()
|
defer i.mux.Unlock()
|
||||||
|
|
||||||
|
err := i.insertRoutingRule(forwardingFormat, iptablesFilterTable, iptablesRoutingForwardingChain, routingFinalForwardJump, pair)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = i.insertRoutingRule(inForwardingFormat, iptablesFilterTable, iptablesRoutingForwardingChain, routingFinalForwardJump, getInPair(pair))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !pair.masquerade {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err = i.insertRoutingRule(natFormat, iptablesNatTable, iptablesRoutingNatChain, routingFinalNatJump, pair)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = i.insertRoutingRule(inNatFormat, iptablesNatTable, iptablesRoutingNatChain, routingFinalNatJump, getInPair(pair))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// insertRoutingRule inserts an iptable rule
|
||||||
|
func (i *iptablesManager) insertRoutingRule(keyFormat, table, chain, jump string, pair routerPair) error {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
prefix := netip.MustParsePrefix(pair.source)
|
prefix := netip.MustParsePrefix(pair.source)
|
||||||
ipVersion := ipv4
|
ipVersion := ipv4
|
||||||
iptablesClient := i.ipv4Client
|
iptablesClient := i.ipv4Client
|
||||||
@@ -320,43 +350,22 @@ func (i *iptablesManager) InsertRoutingRules(pair routerPair) error {
|
|||||||
ipVersion = ipv6
|
ipVersion = ipv6
|
||||||
}
|
}
|
||||||
|
|
||||||
forwardRuleKey := genKey(forwardingFormat, pair.ID)
|
ruleKey := genKey(keyFormat, pair.ID)
|
||||||
forwardRule := genRuleSpec(routingFinalForwardJump, forwardRuleKey, pair.source, pair.destination)
|
rule := genRuleSpec(jump, ruleKey, pair.source, pair.destination)
|
||||||
existingRule, found := i.rules[ipVersion][forwardRuleKey]
|
existingRule, found := i.rules[ipVersion][ruleKey]
|
||||||
if found {
|
if found {
|
||||||
err = iptablesClient.DeleteIfExists(iptablesFilterTable, iptablesRoutingForwardingChain, existingRule...)
|
err = iptablesClient.DeleteIfExists(table, chain, existingRule...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("iptables: error while removing existing forwarding rule for %s: %v", pair.destination, err)
|
return fmt.Errorf("iptables: error while removing existing %s rule for %s: %v", getIptablesRuleType(table), pair.destination, err)
|
||||||
}
|
}
|
||||||
delete(i.rules[ipVersion], forwardRuleKey)
|
delete(i.rules[ipVersion], ruleKey)
|
||||||
}
|
}
|
||||||
err = iptablesClient.Insert(iptablesFilterTable, iptablesRoutingForwardingChain, 1, forwardRule...)
|
err = iptablesClient.Insert(table, chain, 1, rule...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("iptables: error while adding new forwarding rule for %s: %v", pair.destination, err)
|
return fmt.Errorf("iptables: error while adding new %s rule for %s: %v", getIptablesRuleType(table), pair.destination, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
i.rules[ipVersion][forwardRuleKey] = forwardRule
|
i.rules[ipVersion][ruleKey] = rule
|
||||||
|
|
||||||
if !pair.masquerade {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
natRuleKey := genKey(natFormat, pair.ID)
|
|
||||||
natRule := genRuleSpec(routingFinalNatJump, natRuleKey, pair.source, pair.destination)
|
|
||||||
existingRule, found = i.rules[ipVersion][natRuleKey]
|
|
||||||
if found {
|
|
||||||
err = iptablesClient.DeleteIfExists(iptablesNatTable, iptablesRoutingNatChain, existingRule...)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("iptables: error while removing existing nat rulefor %s: %v", pair.destination, err)
|
|
||||||
}
|
|
||||||
delete(i.rules[ipVersion], natRuleKey)
|
|
||||||
}
|
|
||||||
err = iptablesClient.Insert(iptablesNatTable, iptablesRoutingNatChain, 1, natRule...)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("iptables: error while adding new nat rulefor %s: %v", pair.destination, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
i.rules[ipVersion][natRuleKey] = natRule
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -366,7 +375,37 @@ func (i *iptablesManager) RemoveRoutingRules(pair routerPair) error {
|
|||||||
i.mux.Lock()
|
i.mux.Lock()
|
||||||
defer i.mux.Unlock()
|
defer i.mux.Unlock()
|
||||||
|
|
||||||
|
err := i.removeRoutingRule(forwardingFormat, iptablesFilterTable, iptablesRoutingForwardingChain, pair)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = i.removeRoutingRule(inForwardingFormat, iptablesFilterTable, iptablesRoutingForwardingChain, getInPair(pair))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !pair.masquerade {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err = i.removeRoutingRule(natFormat, iptablesNatTable, iptablesRoutingNatChain, pair)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = i.removeRoutingRule(inNatFormat, iptablesNatTable, iptablesRoutingNatChain, getInPair(pair))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// removeRoutingRule removes an iptables rule
|
||||||
|
func (i *iptablesManager) removeRoutingRule(keyFormat, table, chain string, pair routerPair) error {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
prefix := netip.MustParsePrefix(pair.source)
|
prefix := netip.MustParsePrefix(pair.source)
|
||||||
ipVersion := ipv4
|
ipVersion := ipv4
|
||||||
iptablesClient := i.ipv4Client
|
iptablesClient := i.ipv4Client
|
||||||
@@ -375,29 +414,23 @@ func (i *iptablesManager) RemoveRoutingRules(pair routerPair) error {
|
|||||||
ipVersion = ipv6
|
ipVersion = ipv6
|
||||||
}
|
}
|
||||||
|
|
||||||
forwardRuleKey := genKey(forwardingFormat, pair.ID)
|
ruleKey := genKey(keyFormat, pair.ID)
|
||||||
existingRule, found := i.rules[ipVersion][forwardRuleKey]
|
existingRule, found := i.rules[ipVersion][ruleKey]
|
||||||
if found {
|
if found {
|
||||||
err = iptablesClient.DeleteIfExists(iptablesFilterTable, iptablesRoutingForwardingChain, existingRule...)
|
err = iptablesClient.DeleteIfExists(table, chain, existingRule...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("iptables: error while removing existing forwarding rule for %s: %v", pair.destination, err)
|
return fmt.Errorf("iptables: error while removing existing %s rule for %s: %v", getIptablesRuleType(table), pair.destination, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
delete(i.rules[ipVersion], forwardRuleKey)
|
delete(i.rules[ipVersion], ruleKey)
|
||||||
|
|
||||||
if !pair.masquerade {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
natRuleKey := genKey(natFormat, pair.ID)
|
|
||||||
existingRule, found = i.rules[ipVersion][natRuleKey]
|
|
||||||
if found {
|
|
||||||
err = iptablesClient.DeleteIfExists(iptablesNatTable, iptablesRoutingNatChain, existingRule...)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("iptables: error while removing existing nat rule for %s: %v", pair.destination, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
delete(i.rules[ipVersion], natRuleKey)
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getIptablesRuleType(table string) string {
|
||||||
|
ruleType := "forwarding"
|
||||||
|
if table == iptablesNatTable {
|
||||||
|
ruleType = "nat"
|
||||||
|
}
|
||||||
|
return ruleType
|
||||||
|
}
|
||||||
|
|||||||
@@ -159,6 +159,17 @@ func TestIptablesManager_InsertRoutingRules(t *testing.T) {
|
|||||||
require.True(t, found, "forwarding rule should exist in the manager map")
|
require.True(t, found, "forwarding rule should exist in the manager map")
|
||||||
require.Equal(t, forwardRule[:4], foundRule[:4], "stored forwarding rule should match")
|
require.Equal(t, forwardRule[:4], foundRule[:4], "stored forwarding rule should match")
|
||||||
|
|
||||||
|
inForwardRuleKey := genKey(inForwardingFormat, testCase.inputPair.ID)
|
||||||
|
inForwardRule := genRuleSpec(routingFinalForwardJump, inForwardRuleKey, getInPair(testCase.inputPair).source, getInPair(testCase.inputPair).destination)
|
||||||
|
|
||||||
|
exists, err = iptablesClient.Exists(iptablesFilterTable, iptablesRoutingForwardingChain, inForwardRule...)
|
||||||
|
require.NoError(t, err, "should be able to query the iptables %s %s table and %s chain", testCase.ipVersion, iptablesFilterTable, iptablesRoutingForwardingChain)
|
||||||
|
require.True(t, exists, "income forwarding rule should exist")
|
||||||
|
|
||||||
|
foundRule, found = manager.rules[testCase.ipVersion][inForwardRuleKey]
|
||||||
|
require.True(t, found, "income forwarding rule should exist in the manager map")
|
||||||
|
require.Equal(t, inForwardRule[:4], foundRule[:4], "stored income forwarding rule should match")
|
||||||
|
|
||||||
natRuleKey := genKey(natFormat, testCase.inputPair.ID)
|
natRuleKey := genKey(natFormat, testCase.inputPair.ID)
|
||||||
natRule := genRuleSpec(routingFinalNatJump, natRuleKey, testCase.inputPair.source, testCase.inputPair.destination)
|
natRule := genRuleSpec(routingFinalNatJump, natRuleKey, testCase.inputPair.source, testCase.inputPair.destination)
|
||||||
|
|
||||||
@@ -172,7 +183,23 @@ func TestIptablesManager_InsertRoutingRules(t *testing.T) {
|
|||||||
} else {
|
} else {
|
||||||
require.False(t, exists, "nat rule should not be created")
|
require.False(t, exists, "nat rule should not be created")
|
||||||
_, foundNat := manager.rules[testCase.ipVersion][natRuleKey]
|
_, foundNat := manager.rules[testCase.ipVersion][natRuleKey]
|
||||||
require.False(t, foundNat, "nat rule should exist in the map")
|
require.False(t, foundNat, "nat rule should not exist in the map")
|
||||||
|
}
|
||||||
|
|
||||||
|
inNatRuleKey := genKey(inNatFormat, testCase.inputPair.ID)
|
||||||
|
inNatRule := genRuleSpec(routingFinalNatJump, inNatRuleKey, getInPair(testCase.inputPair).source, getInPair(testCase.inputPair).destination)
|
||||||
|
|
||||||
|
exists, err = iptablesClient.Exists(iptablesNatTable, iptablesRoutingNatChain, inNatRule...)
|
||||||
|
require.NoError(t, err, "should be able to query the iptables %s %s table and %s chain", testCase.ipVersion, iptablesNatTable, iptablesRoutingNatChain)
|
||||||
|
if testCase.inputPair.masquerade {
|
||||||
|
require.True(t, exists, "income nat rule should be created")
|
||||||
|
foundNatRule, foundNat := manager.rules[testCase.ipVersion][inNatRuleKey]
|
||||||
|
require.True(t, foundNat, "income nat rule should exist in the map")
|
||||||
|
require.Equal(t, inNatRule[:4], foundNatRule[:4], "stored income nat rule should match")
|
||||||
|
} else {
|
||||||
|
require.False(t, exists, "nat rule should not be created")
|
||||||
|
_, foundNat := manager.rules[testCase.ipVersion][inNatRuleKey]
|
||||||
|
require.False(t, foundNat, "income nat rule should not exist in the map")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -213,12 +240,24 @@ func TestIptablesManager_RemoveRoutingRules(t *testing.T) {
|
|||||||
err = iptablesClient.Insert(iptablesFilterTable, iptablesRoutingForwardingChain, 1, forwardRule...)
|
err = iptablesClient.Insert(iptablesFilterTable, iptablesRoutingForwardingChain, 1, forwardRule...)
|
||||||
require.NoError(t, err, "inserting rule should not return error")
|
require.NoError(t, err, "inserting rule should not return error")
|
||||||
|
|
||||||
|
inForwardRuleKey := genKey(inForwardingFormat, testCase.inputPair.ID)
|
||||||
|
inForwardRule := genRuleSpec(routingFinalForwardJump, inForwardRuleKey, getInPair(testCase.inputPair).source, getInPair(testCase.inputPair).destination)
|
||||||
|
|
||||||
|
err = iptablesClient.Insert(iptablesFilterTable, iptablesRoutingForwardingChain, 1, inForwardRule...)
|
||||||
|
require.NoError(t, err, "inserting rule should not return error")
|
||||||
|
|
||||||
natRuleKey := genKey(natFormat, testCase.inputPair.ID)
|
natRuleKey := genKey(natFormat, testCase.inputPair.ID)
|
||||||
natRule := genRuleSpec(routingFinalNatJump, natRuleKey, testCase.inputPair.source, testCase.inputPair.destination)
|
natRule := genRuleSpec(routingFinalNatJump, natRuleKey, testCase.inputPair.source, testCase.inputPair.destination)
|
||||||
|
|
||||||
err = iptablesClient.Insert(iptablesNatTable, iptablesRoutingNatChain, 1, natRule...)
|
err = iptablesClient.Insert(iptablesNatTable, iptablesRoutingNatChain, 1, natRule...)
|
||||||
require.NoError(t, err, "inserting rule should not return error")
|
require.NoError(t, err, "inserting rule should not return error")
|
||||||
|
|
||||||
|
inNatRuleKey := genKey(inNatFormat, testCase.inputPair.ID)
|
||||||
|
inNatRule := genRuleSpec(routingFinalNatJump, inNatRuleKey, getInPair(testCase.inputPair).source, getInPair(testCase.inputPair).destination)
|
||||||
|
|
||||||
|
err = iptablesClient.Insert(iptablesNatTable, iptablesRoutingNatChain, 1, inNatRule...)
|
||||||
|
require.NoError(t, err, "inserting rule should not return error")
|
||||||
|
|
||||||
delete(manager.rules, ipv4)
|
delete(manager.rules, ipv4)
|
||||||
delete(manager.rules, ipv6)
|
delete(manager.rules, ipv6)
|
||||||
|
|
||||||
@@ -235,12 +274,26 @@ func TestIptablesManager_RemoveRoutingRules(t *testing.T) {
|
|||||||
_, found := manager.rules[testCase.ipVersion][forwardRuleKey]
|
_, found := manager.rules[testCase.ipVersion][forwardRuleKey]
|
||||||
require.False(t, found, "forwarding rule should exist in the manager map")
|
require.False(t, found, "forwarding rule should exist in the manager map")
|
||||||
|
|
||||||
|
exists, err = iptablesClient.Exists(iptablesFilterTable, iptablesRoutingForwardingChain, inForwardRule...)
|
||||||
|
require.NoError(t, err, "should be able to query the iptables %s %s table and %s chain", testCase.ipVersion, iptablesFilterTable, iptablesRoutingForwardingChain)
|
||||||
|
require.False(t, exists, "income forwarding rule should not exist")
|
||||||
|
|
||||||
|
_, found = manager.rules[testCase.ipVersion][inForwardRuleKey]
|
||||||
|
require.False(t, found, "income forwarding rule should exist in the manager map")
|
||||||
|
|
||||||
exists, err = iptablesClient.Exists(iptablesNatTable, iptablesRoutingNatChain, natRule...)
|
exists, err = iptablesClient.Exists(iptablesNatTable, iptablesRoutingNatChain, natRule...)
|
||||||
require.NoError(t, err, "should be able to query the iptables %s %s table and %s chain", testCase.ipVersion, iptablesNatTable, iptablesRoutingNatChain)
|
require.NoError(t, err, "should be able to query the iptables %s %s table and %s chain", testCase.ipVersion, iptablesNatTable, iptablesRoutingNatChain)
|
||||||
require.False(t, exists, "nat rule should not exist")
|
require.False(t, exists, "nat rule should not exist")
|
||||||
|
|
||||||
_, found = manager.rules[testCase.ipVersion][natRuleKey]
|
_, found = manager.rules[testCase.ipVersion][natRuleKey]
|
||||||
require.False(t, found, "forwarding rule should exist in the manager map")
|
require.False(t, found, "nat rule should exist in the manager map")
|
||||||
|
|
||||||
|
exists, err = iptablesClient.Exists(iptablesNatTable, iptablesRoutingNatChain, inNatRule...)
|
||||||
|
require.NoError(t, err, "should be able to query the iptables %s %s table and %s chain", testCase.ipVersion, iptablesNatTable, iptablesRoutingNatChain)
|
||||||
|
require.False(t, exists, "income nat rule should not exist")
|
||||||
|
|
||||||
|
_, found = manager.rules[testCase.ipVersion][inNatRuleKey]
|
||||||
|
require.False(t, found, "income nat rule should exist in the manager map")
|
||||||
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import (
|
|||||||
)
|
)
|
||||||
import "github.com/google/nftables"
|
import "github.com/google/nftables"
|
||||||
|
|
||||||
//
|
|
||||||
const (
|
const (
|
||||||
nftablesTable = "netbird-rt"
|
nftablesTable = "netbird-rt"
|
||||||
nftablesRoutingForwardingChain = "netbird-rt-fwd"
|
nftablesRoutingForwardingChain = "netbird-rt-fwd"
|
||||||
@@ -84,8 +83,10 @@ func (n *nftablesManager) CleanRoutingRules() {
|
|||||||
n.mux.Lock()
|
n.mux.Lock()
|
||||||
defer n.mux.Unlock()
|
defer n.mux.Unlock()
|
||||||
log.Debug("flushing tables")
|
log.Debug("flushing tables")
|
||||||
n.conn.FlushTable(n.tableIPv6)
|
if n.tableIPv4 != nil && n.tableIPv6 != nil {
|
||||||
n.conn.FlushTable(n.tableIPv4)
|
n.conn.FlushTable(n.tableIPv6)
|
||||||
|
n.conn.FlushTable(n.tableIPv4)
|
||||||
|
}
|
||||||
log.Debugf("flushing tables result in: %v error", n.conn.Flush())
|
log.Debugf("flushing tables result in: %v error", n.conn.Flush())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -246,53 +247,77 @@ func (n *nftablesManager) InsertRoutingRules(pair routerPair) error {
|
|||||||
n.mux.Lock()
|
n.mux.Lock()
|
||||||
defer n.mux.Unlock()
|
defer n.mux.Unlock()
|
||||||
|
|
||||||
|
err := n.refreshRulesMap()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = n.insertRoutingRule(forwardingFormat, nftablesRoutingForwardingChain, pair, false)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = n.insertRoutingRule(inForwardingFormat, nftablesRoutingForwardingChain, getInPair(pair), false)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if pair.masquerade {
|
||||||
|
err = n.insertRoutingRule(natFormat, nftablesRoutingNatChain, pair, true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = n.insertRoutingRule(inNatFormat, nftablesRoutingNatChain, getInPair(pair), true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = n.conn.Flush()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("nftables: unable to insert rules for %s: %v", pair.destination, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// insertRoutingRule inserts a nftable rule to the conn client flush queue
|
||||||
|
func (n *nftablesManager) insertRoutingRule(format, chain string, pair routerPair, isNat bool) error {
|
||||||
|
|
||||||
prefix := netip.MustParsePrefix(pair.source)
|
prefix := netip.MustParsePrefix(pair.source)
|
||||||
|
|
||||||
sourceExp := generateCIDRMatcherExpressions("source", pair.source)
|
sourceExp := generateCIDRMatcherExpressions("source", pair.source)
|
||||||
destExp := generateCIDRMatcherExpressions("destination", pair.destination)
|
destExp := generateCIDRMatcherExpressions("destination", pair.destination)
|
||||||
|
|
||||||
forwardExp := append(sourceExp, append(destExp, exprCounterAccept...)...)
|
var expression []expr.Any
|
||||||
fwdKey := genKey(forwardingFormat, pair.ID)
|
if isNat {
|
||||||
if prefix.Addr().Unmap().Is4() {
|
expression = append(sourceExp, append(destExp, &expr.Counter{}, &expr.Masq{})...)
|
||||||
n.rules[fwdKey] = n.conn.InsertRule(&nftables.Rule{
|
|
||||||
Table: n.tableIPv4,
|
|
||||||
Chain: n.chains[ipv4][nftablesRoutingForwardingChain],
|
|
||||||
Exprs: forwardExp,
|
|
||||||
UserData: []byte(fwdKey),
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
n.rules[fwdKey] = n.conn.InsertRule(&nftables.Rule{
|
expression = append(sourceExp, append(destExp, exprCounterAccept...)...)
|
||||||
Table: n.tableIPv6,
|
|
||||||
Chain: n.chains[ipv6][nftablesRoutingForwardingChain],
|
|
||||||
Exprs: forwardExp,
|
|
||||||
UserData: []byte(fwdKey),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if pair.masquerade {
|
ruleKey := genKey(format, pair.ID)
|
||||||
natExp := append(sourceExp, append(destExp, &expr.Counter{}, &expr.Masq{})...)
|
|
||||||
natKey := genKey(natFormat, pair.ID)
|
|
||||||
|
|
||||||
if prefix.Addr().Unmap().Is4() {
|
_, exists := n.rules[ruleKey]
|
||||||
n.rules[natKey] = n.conn.InsertRule(&nftables.Rule{
|
if exists {
|
||||||
Table: n.tableIPv4,
|
err := n.removeRoutingRule(format, pair)
|
||||||
Chain: n.chains[ipv4][nftablesRoutingNatChain],
|
if err != nil {
|
||||||
Exprs: natExp,
|
return err
|
||||||
UserData: []byte(natKey),
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
n.rules[natKey] = n.conn.InsertRule(&nftables.Rule{
|
|
||||||
Table: n.tableIPv6,
|
|
||||||
Chain: n.chains[ipv6][nftablesRoutingNatChain],
|
|
||||||
Exprs: natExp,
|
|
||||||
UserData: []byte(natKey),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err := n.conn.Flush()
|
if prefix.Addr().Unmap().Is4() {
|
||||||
if err != nil {
|
n.rules[ruleKey] = n.conn.InsertRule(&nftables.Rule{
|
||||||
return fmt.Errorf("nftables: unable to insert rules for %s: %v", pair.destination, err)
|
Table: n.tableIPv4,
|
||||||
|
Chain: n.chains[ipv4][chain],
|
||||||
|
Exprs: expression,
|
||||||
|
UserData: []byte(ruleKey),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
n.rules[ruleKey] = n.conn.InsertRule(&nftables.Rule{
|
||||||
|
Table: n.tableIPv6,
|
||||||
|
Chain: n.chains[ipv6][chain],
|
||||||
|
Exprs: expression,
|
||||||
|
UserData: []byte(ruleKey),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -307,26 +332,26 @@ func (n *nftablesManager) RemoveRoutingRules(pair routerPair) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fwdKey := genKey(forwardingFormat, pair.ID)
|
err = n.removeRoutingRule(forwardingFormat, pair)
|
||||||
natKey := genKey(natFormat, pair.ID)
|
if err != nil {
|
||||||
fwdRule, found := n.rules[fwdKey]
|
return err
|
||||||
if found {
|
|
||||||
err = n.conn.DelRule(fwdRule)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("nftables: unable to remove forwarding rule for %s: %v", pair.destination, err)
|
|
||||||
}
|
|
||||||
log.Debugf("nftables: removing forwarding rule for %s", pair.destination)
|
|
||||||
delete(n.rules, fwdKey)
|
|
||||||
}
|
}
|
||||||
natRule, found := n.rules[natKey]
|
|
||||||
if found {
|
err = n.removeRoutingRule(inForwardingFormat, getInPair(pair))
|
||||||
err = n.conn.DelRule(natRule)
|
if err != nil {
|
||||||
if err != nil {
|
return err
|
||||||
return fmt.Errorf("nftables: unable to remove nat rule for %s: %v", pair.destination, err)
|
|
||||||
}
|
|
||||||
log.Debugf("nftables: removing nat rule for %s", pair.destination)
|
|
||||||
delete(n.rules, natKey)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = n.removeRoutingRule(natFormat, pair)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = n.removeRoutingRule(inNatFormat, getInPair(pair))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
err = n.conn.Flush()
|
err = n.conn.Flush()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("nftables: received error while applying rule removal for %s: %v", pair.destination, err)
|
return fmt.Errorf("nftables: received error while applying rule removal for %s: %v", pair.destination, err)
|
||||||
@@ -335,6 +360,29 @@ func (n *nftablesManager) RemoveRoutingRules(pair routerPair) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// removeRoutingRule add a nftable rule to the removal queue and delete from rules map
|
||||||
|
func (n *nftablesManager) removeRoutingRule(format string, pair routerPair) error {
|
||||||
|
ruleKey := genKey(format, pair.ID)
|
||||||
|
|
||||||
|
rule, found := n.rules[ruleKey]
|
||||||
|
if found {
|
||||||
|
ruleType := "forwarding"
|
||||||
|
if rule.Chain.Type == nftables.ChainTypeNAT {
|
||||||
|
ruleType = "nat"
|
||||||
|
}
|
||||||
|
|
||||||
|
err := n.conn.DelRule(rule)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("nftables: unable to remove %s rule for %s: %v", ruleType, pair.destination, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf("nftables: removing %s rule for %s", ruleType, pair.destination)
|
||||||
|
|
||||||
|
delete(n.rules, ruleKey)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// getPayloadDirectives get expression directives based on ip version and direction
|
// getPayloadDirectives get expression directives based on ip version and direction
|
||||||
func getPayloadDirectives(direction string, isIPv4 bool, isIPv6 bool) (uint32, uint32, []byte) {
|
func getPayloadDirectives(direction string, isIPv4 bool, isIPv6 bool) (uint32, uint32, []byte) {
|
||||||
switch {
|
switch {
|
||||||
|
|||||||
@@ -189,6 +189,45 @@ func TestNftablesManager_InsertRoutingRules(t *testing.T) {
|
|||||||
}
|
}
|
||||||
require.Equal(t, 1, found, "should find at least 1 rule to test")
|
require.Equal(t, 1, found, "should find at least 1 rule to test")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sourceExp = generateCIDRMatcherExpressions("source", getInPair(testCase.inputPair).source)
|
||||||
|
destExp = generateCIDRMatcherExpressions("destination", getInPair(testCase.inputPair).destination)
|
||||||
|
testingExpression = append(sourceExp, destExp...)
|
||||||
|
inFwdRuleKey := genKey(inForwardingFormat, testCase.inputPair.ID)
|
||||||
|
|
||||||
|
found = 0
|
||||||
|
for _, registeredChains := range manager.chains {
|
||||||
|
for _, chain := range registeredChains {
|
||||||
|
rules, err := nftablesTestingClient.GetRules(chain.Table, chain)
|
||||||
|
require.NoError(t, err, "should list rules for %s table and %s chain", chain.Table.Name, chain.Name)
|
||||||
|
for _, rule := range rules {
|
||||||
|
if len(rule.UserData) > 0 && string(rule.UserData) == inFwdRuleKey {
|
||||||
|
require.ElementsMatchf(t, rule.Exprs[:len(testingExpression)], testingExpression, "income forwarding rule elements should match")
|
||||||
|
found = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
require.Equal(t, 1, found, "should find at least 1 rule to test")
|
||||||
|
|
||||||
|
if testCase.inputPair.masquerade {
|
||||||
|
inNatRuleKey := genKey(inNatFormat, testCase.inputPair.ID)
|
||||||
|
found := 0
|
||||||
|
for _, registeredChains := range manager.chains {
|
||||||
|
for _, chain := range registeredChains {
|
||||||
|
rules, err := nftablesTestingClient.GetRules(chain.Table, chain)
|
||||||
|
require.NoError(t, err, "should list rules for %s table and %s chain", chain.Table.Name, chain.Name)
|
||||||
|
for _, rule := range rules {
|
||||||
|
if len(rule.UserData) > 0 && string(rule.UserData) == inNatRuleKey {
|
||||||
|
require.ElementsMatchf(t, rule.Exprs[:len(testingExpression)], testingExpression, "income nat rule elements should match")
|
||||||
|
found = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
require.Equal(t, 1, found, "should find at least 1 rule to test")
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -241,6 +280,28 @@ func TestNftablesManager_RemoveRoutingRules(t *testing.T) {
|
|||||||
UserData: []byte(natRuleKey),
|
UserData: []byte(natRuleKey),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
sourceExp = generateCIDRMatcherExpressions("source", getInPair(testCase.inputPair).source)
|
||||||
|
destExp = generateCIDRMatcherExpressions("destination", getInPair(testCase.inputPair).destination)
|
||||||
|
|
||||||
|
forwardExp = append(sourceExp, append(destExp, exprCounterAccept...)...)
|
||||||
|
inForwardRuleKey := genKey(inForwardingFormat, testCase.inputPair.ID)
|
||||||
|
insertedInForwarding := nftablesTestingClient.InsertRule(&nftables.Rule{
|
||||||
|
Table: table,
|
||||||
|
Chain: manager.chains[testCase.ipVersion][nftablesRoutingForwardingChain],
|
||||||
|
Exprs: forwardExp,
|
||||||
|
UserData: []byte(inForwardRuleKey),
|
||||||
|
})
|
||||||
|
|
||||||
|
natExp = append(sourceExp, append(destExp, &expr.Counter{}, &expr.Masq{})...)
|
||||||
|
inNatRuleKey := genKey(inNatFormat, testCase.inputPair.ID)
|
||||||
|
|
||||||
|
insertedInNat := nftablesTestingClient.InsertRule(&nftables.Rule{
|
||||||
|
Table: table,
|
||||||
|
Chain: manager.chains[testCase.ipVersion][nftablesRoutingNatChain],
|
||||||
|
Exprs: natExp,
|
||||||
|
UserData: []byte(inNatRuleKey),
|
||||||
|
})
|
||||||
|
|
||||||
err = nftablesTestingClient.Flush()
|
err = nftablesTestingClient.Flush()
|
||||||
require.NoError(t, err, "shouldn't return error")
|
require.NoError(t, err, "shouldn't return error")
|
||||||
|
|
||||||
@@ -259,8 +320,10 @@ func TestNftablesManager_RemoveRoutingRules(t *testing.T) {
|
|||||||
require.NoError(t, err, "should list rules for %s table and %s chain", chain.Table.Name, chain.Name)
|
require.NoError(t, err, "should list rules for %s table and %s chain", chain.Table.Name, chain.Name)
|
||||||
for _, rule := range rules {
|
for _, rule := range rules {
|
||||||
if len(rule.UserData) > 0 {
|
if len(rule.UserData) > 0 {
|
||||||
require.NotEqual(t, insertedForwarding.UserData, rule.UserData, "forwarding rule should exist")
|
require.NotEqual(t, insertedForwarding.UserData, rule.UserData, "forwarding rule should not exist")
|
||||||
require.NotEqual(t, insertedNat.UserData, rule.UserData, "nat rule should exist")
|
require.NotEqual(t, insertedNat.UserData, rule.UserData, "nat rule should not exist")
|
||||||
|
require.NotEqual(t, insertedInForwarding.UserData, rule.UserData, "income forwarding rule should not exist")
|
||||||
|
require.NotEqual(t, insertedInNat.UserData, rule.UserData, "income nat rule should not exist")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ func addToRouteTableIfNoExists(prefix netip.Prefix, addr string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if prefixGateway != nil && !prefixGateway.Equal(gateway) {
|
if prefixGateway != nil && !prefixGateway.Equal(gateway) {
|
||||||
log.Warnf("route for network %s already exist and is pointing to the gateway: %s, won't add another one", prefix, prefixGateway)
|
log.Warnf("skipping adding a new route for network %s because it already exists and is pointing to the non default gateway: %s", prefix, prefixGateway)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return addToRouteTable(prefix, addr)
|
return addToRouteTable(prefix, addr)
|
||||||
@@ -45,11 +45,14 @@ func getExistingRIBRouteGateway(prefix netip.Prefix) (net.IP, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
_, _, localGatewayAddress, err := r.Route(prefix.Addr().AsSlice())
|
_, gateway, preferredSrc, err := r.Route(prefix.Addr().AsSlice())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("getting routes returned an error: %v", err)
|
log.Errorf("getting routes returned an error: %v", err)
|
||||||
return nil, errRouteNotFound
|
return nil, errRouteNotFound
|
||||||
}
|
}
|
||||||
|
if gateway == nil {
|
||||||
|
return preferredSrc, nil
|
||||||
|
}
|
||||||
|
|
||||||
return localGatewayAddress, nil
|
return gateway, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"github.com/netbirdio/netbird/iface"
|
"github.com/netbirdio/netbird/iface"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
@@ -66,3 +67,45 @@ func TestAddRemoveRoutes(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetExistingRIBRouteGateway(t *testing.T) {
|
||||||
|
gateway, err := getExistingRIBRouteGateway(netip.MustParsePrefix("0.0.0.0/0"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("shouldn't return error when fetching the gateway: ", err)
|
||||||
|
}
|
||||||
|
if gateway == nil {
|
||||||
|
t.Fatal("should return a gateway")
|
||||||
|
}
|
||||||
|
addresses, err := net.InterfaceAddrs()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("shouldn't return error when fetching interface addresses: ", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var testingIP string
|
||||||
|
var testingPrefix netip.Prefix
|
||||||
|
for _, address := range addresses {
|
||||||
|
if address.Network() != "ip+net" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
prefix := netip.MustParsePrefix(address.String())
|
||||||
|
if !prefix.Addr().IsLoopback() && prefix.Addr().Is4() {
|
||||||
|
testingIP = prefix.Addr().String()
|
||||||
|
testingPrefix = prefix.Masked()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
localIP, err := getExistingRIBRouteGateway(testingPrefix)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("shouldn't return error: ", err)
|
||||||
|
}
|
||||||
|
if localIP == nil {
|
||||||
|
t.Fatal("should return a gateway for local network")
|
||||||
|
}
|
||||||
|
if localIP.String() == gateway.String() {
|
||||||
|
t.Fatal("local ip should not match with gateway IP")
|
||||||
|
}
|
||||||
|
if localIP.String() != testingIP {
|
||||||
|
t.Fatalf("local ip should match with testing IP: want %s got %s", testingIP, localIP.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,36 +1,17 @@
|
|||||||
package system
|
package system
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"golang.org/x/sys/windows/registry"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetInfo retrieves and parses the system information
|
// GetInfo retrieves and parses the system information
|
||||||
func GetInfo(ctx context.Context) *Info {
|
func GetInfo(ctx context.Context) *Info {
|
||||||
cmd := exec.Command("cmd", "ver")
|
ver := getOSVersion()
|
||||||
cmd.Stdin = strings.NewReader("some")
|
|
||||||
var out bytes.Buffer
|
|
||||||
var stderr bytes.Buffer
|
|
||||||
cmd.Stdout = &out
|
|
||||||
cmd.Stderr = &stderr
|
|
||||||
err := cmd.Run()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
osStr := strings.Replace(out.String(), "\n", "", -1)
|
|
||||||
osStr = strings.Replace(osStr, "\r\n", "", -1)
|
|
||||||
tmp1 := strings.Index(osStr, "[Version")
|
|
||||||
tmp2 := strings.Index(osStr, "]")
|
|
||||||
var ver string
|
|
||||||
if tmp1 == -1 || tmp2 == -1 {
|
|
||||||
ver = "unknown"
|
|
||||||
} else {
|
|
||||||
ver = osStr[tmp1+9 : tmp2]
|
|
||||||
}
|
|
||||||
gio := &Info{Kernel: "windows", OSVersion: ver, Core: ver, Platform: "unknown", OS: "windows", GoOS: runtime.GOOS, CPUs: runtime.NumCPU()}
|
gio := &Info{Kernel: "windows", OSVersion: ver, Core: ver, Platform: "unknown", OS: "windows", GoOS: runtime.GOOS, CPUs: runtime.NumCPU()}
|
||||||
gio.Hostname, _ = os.Hostname()
|
gio.Hostname, _ = os.Hostname()
|
||||||
gio.WiretrusteeVersion = NetbirdVersion()
|
gio.WiretrusteeVersion = NetbirdVersion()
|
||||||
@@ -38,3 +19,37 @@ func GetInfo(ctx context.Context) *Info {
|
|||||||
|
|
||||||
return gio
|
return gio
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getOSVersion() string {
|
||||||
|
k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion`, registry.QUERY_VALUE)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
return "0.0.0.0"
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
deferErr := k.Close()
|
||||||
|
if deferErr != nil {
|
||||||
|
log.Error(deferErr)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
major, _, err := k.GetIntegerValue("CurrentMajorVersionNumber")
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
minor, _, err := k.GetIntegerValue("CurrentMinorVersionNumber")
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
build, _, err := k.GetStringValue("CurrentBuildNumber")
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
// Update Build Revision
|
||||||
|
ubr, _, err := k.GetIntegerValue("UBR")
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
ver := fmt.Sprintf("%d.%d.%s.%d", major, minor, build, ubr)
|
||||||
|
return ver
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/netbirdio/netbird/client/system"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path"
|
"path"
|
||||||
@@ -18,6 +17,8 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/client/system"
|
||||||
|
|
||||||
"github.com/cenkalti/backoff/v4"
|
"github.com/cenkalti/backoff/v4"
|
||||||
|
|
||||||
_ "embed"
|
_ "embed"
|
||||||
@@ -61,6 +62,8 @@ func main() {
|
|||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
a := app.New()
|
a := app.New()
|
||||||
|
a.SetIcon(fyne.NewStaticResource("netbird", iconDisconnectedPNG))
|
||||||
|
|
||||||
client := newServiceClient(daemonAddr, a, showSettings)
|
client := newServiceClient(daemonAddr, a, showSettings)
|
||||||
if showSettings {
|
if showSettings {
|
||||||
a.Run()
|
a.Run()
|
||||||
@@ -113,7 +116,7 @@ type serviceClient struct {
|
|||||||
iLogFile *widget.Entry
|
iLogFile *widget.Entry
|
||||||
iPreSharedKey *widget.Entry
|
iPreSharedKey *widget.Entry
|
||||||
|
|
||||||
// observable settings over correspondign iMngURL and iPreSharedKey values.
|
// observable settings over corresponding iMngURL and iPreSharedKey values.
|
||||||
managementURL string
|
managementURL string
|
||||||
preSharedKey string
|
preSharedKey string
|
||||||
adminURL string
|
adminURL string
|
||||||
@@ -121,7 +124,7 @@ type serviceClient struct {
|
|||||||
|
|
||||||
// newServiceClient instance constructor
|
// newServiceClient instance constructor
|
||||||
//
|
//
|
||||||
// This constructor olso build UI elements for settings window.
|
// This constructor also builds the UI elements for the settings window.
|
||||||
func newServiceClient(addr string, a fyne.App, showSettings bool) *serviceClient {
|
func newServiceClient(addr string, a fyne.App, showSettings bool) *serviceClient {
|
||||||
s := &serviceClient{
|
s := &serviceClient{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
@@ -149,7 +152,7 @@ func newServiceClient(addr string, a fyne.App, showSettings bool) *serviceClient
|
|||||||
|
|
||||||
func (s *serviceClient) showUIElements() {
|
func (s *serviceClient) showUIElements() {
|
||||||
// add settings window UI elements.
|
// add settings window UI elements.
|
||||||
s.wSettings = s.app.NewWindow("Settings")
|
s.wSettings = s.app.NewWindow("NetBird Settings")
|
||||||
s.iMngURL = widget.NewEntry()
|
s.iMngURL = widget.NewEntry()
|
||||||
s.iAdminURL = widget.NewEntry()
|
s.iAdminURL = widget.NewEntry()
|
||||||
s.iConfigFile = widget.NewEntry()
|
s.iConfigFile = widget.NewEntry()
|
||||||
@@ -324,13 +327,15 @@ func (s *serviceClient) updateStatus() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if status.Status == string(internal.StatusConnected) {
|
if status.Status == string(internal.StatusConnected) && !s.mUp.Disabled() {
|
||||||
systray.SetIcon(s.icConnected)
|
systray.SetIcon(s.icConnected)
|
||||||
|
systray.SetTooltip("NetBird (Connected)")
|
||||||
s.mStatus.SetTitle("Connected")
|
s.mStatus.SetTitle("Connected")
|
||||||
s.mUp.Disable()
|
s.mUp.Disable()
|
||||||
s.mDown.Enable()
|
s.mDown.Enable()
|
||||||
} else {
|
} else if status.Status != string(internal.StatusConnected) && s.mUp.Disabled() {
|
||||||
systray.SetIcon(s.icDisconnected)
|
systray.SetIcon(s.icDisconnected)
|
||||||
|
systray.SetTooltip("NetBird (Disconnected)")
|
||||||
s.mStatus.SetTitle("Disconnected")
|
s.mStatus.SetTitle("Disconnected")
|
||||||
s.mDown.Disable()
|
s.mDown.Disable()
|
||||||
s.mUp.Enable()
|
s.mUp.Enable()
|
||||||
@@ -355,6 +360,7 @@ func (s *serviceClient) updateStatus() error {
|
|||||||
|
|
||||||
func (s *serviceClient) onTrayReady() {
|
func (s *serviceClient) onTrayReady() {
|
||||||
systray.SetIcon(s.icDisconnected)
|
systray.SetIcon(s.icDisconnected)
|
||||||
|
systray.SetTooltip("NetBird")
|
||||||
|
|
||||||
// setup systray menu items
|
// setup systray menu items
|
||||||
s.mStatus = systray.AddMenuItem("Disconnected", "Disconnected")
|
s.mStatus = systray.AddMenuItem("Disconnected", "Disconnected")
|
||||||
|
|||||||
109
dns/dns.go
Normal file
109
dns/dns.go
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
// Package dns implement dns types and standard methods and functions
|
||||||
|
// to parse and normalize dns records and configuration
|
||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
"golang.org/x/net/idna"
|
||||||
|
"net"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// DefaultDNSPort well-known port number
|
||||||
|
DefaultDNSPort = 53
|
||||||
|
// RootZone is a string representation of the root zone
|
||||||
|
RootZone = "."
|
||||||
|
// DefaultClass is the class supported by the system
|
||||||
|
DefaultClass = "IN"
|
||||||
|
)
|
||||||
|
|
||||||
|
const invalidHostLabel = "[^a-zA-Z0-9-]+"
|
||||||
|
|
||||||
|
// Config represents a dns configuration that is exchanged between management and peers
|
||||||
|
type Config struct {
|
||||||
|
// ServiceEnable indicates if the service should be enabled
|
||||||
|
ServiceEnable bool
|
||||||
|
// NameServerGroups contains a list of nameserver group
|
||||||
|
NameServerGroups []*NameServerGroup
|
||||||
|
// CustomZones contains a list of custom zone
|
||||||
|
CustomZones []CustomZone
|
||||||
|
}
|
||||||
|
|
||||||
|
// CustomZone represents a custom zone to be resolved by the dns server
|
||||||
|
type CustomZone struct {
|
||||||
|
// Domain is the zone's domain
|
||||||
|
Domain string
|
||||||
|
// Records custom zone records
|
||||||
|
Records []SimpleRecord
|
||||||
|
}
|
||||||
|
|
||||||
|
// SimpleRecord provides a simple DNS record specification for CNAME, A and AAAA records
|
||||||
|
type SimpleRecord struct {
|
||||||
|
// Name domain name
|
||||||
|
Name string
|
||||||
|
// Type of record, 1 for A, 5 for CNAME, 28 for AAAA. see https://pkg.go.dev/github.com/miekg/dns@v1.1.41#pkg-constants
|
||||||
|
Type int
|
||||||
|
// Class dns class, currently use the DefaultClass for all records
|
||||||
|
Class string
|
||||||
|
// TTL time-to-live for the record
|
||||||
|
TTL int
|
||||||
|
// RData is the actual value resolved in a dns query
|
||||||
|
RData string
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a string of the simple record formatted as:
|
||||||
|
// <Name> <TTL> <Class> <Type> <RDATA>
|
||||||
|
func (s SimpleRecord) String() string {
|
||||||
|
fqdn := dns.Fqdn(s.Name)
|
||||||
|
return fmt.Sprintf("%s %d %s %s %s", fqdn, s.TTL, s.Class, dns.Type(s.Type).String(), s.RData)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len returns the length of the RData field, based on its type
|
||||||
|
func (s SimpleRecord) Len() uint16 {
|
||||||
|
emptyString := s.RData == ""
|
||||||
|
switch s.Type {
|
||||||
|
case 1:
|
||||||
|
if emptyString {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return net.IPv4len
|
||||||
|
case 5:
|
||||||
|
if emptyString || s.RData == "." {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return uint16(len(s.RData) + 1)
|
||||||
|
case 28:
|
||||||
|
if emptyString {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return net.IPv6len
|
||||||
|
default:
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetParsedDomainLabel returns a domain label with max 59 characters,
|
||||||
|
// parsed for old Hosts.txt requirements, and converted to ASCII and lowercase
|
||||||
|
func GetParsedDomainLabel(name string) (string, error) {
|
||||||
|
labels := dns.SplitDomainName(name)
|
||||||
|
if len(labels) == 0 {
|
||||||
|
return "", fmt.Errorf("got empty label list for name \"%s\"", name)
|
||||||
|
}
|
||||||
|
rawLabel := labels[0]
|
||||||
|
ascii, err := idna.Punycode.ToASCII(rawLabel)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("unable to convert host lavel to ASCII, error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
invalidHostMatcher := regexp.MustCompile(invalidHostLabel)
|
||||||
|
|
||||||
|
validHost := strings.ToLower(invalidHostMatcher.ReplaceAllString(ascii, "-"))
|
||||||
|
if len(validHost) > 58 {
|
||||||
|
validHost = validHost[:59]
|
||||||
|
}
|
||||||
|
|
||||||
|
return validHost, nil
|
||||||
|
}
|
||||||
192
dns/nameserver.go
Normal file
192
dns/nameserver.go
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/netip"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// InvalidNameServerType invalid nameserver type
|
||||||
|
InvalidNameServerType NameServerType = iota
|
||||||
|
// UDPNameServerType udp nameserver type
|
||||||
|
UDPNameServerType
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// MaxGroupNameChar maximum group name size
|
||||||
|
MaxGroupNameChar = 40
|
||||||
|
// InvalidNameServerTypeString invalid nameserver type as string
|
||||||
|
InvalidNameServerTypeString = "invalid"
|
||||||
|
// UDPNameServerTypeString udp nameserver type as string
|
||||||
|
UDPNameServerTypeString = "udp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NameServerType nameserver type
|
||||||
|
type NameServerType int
|
||||||
|
|
||||||
|
// String returns nameserver type string
|
||||||
|
func (n NameServerType) String() string {
|
||||||
|
switch n {
|
||||||
|
case UDPNameServerType:
|
||||||
|
return UDPNameServerTypeString
|
||||||
|
default:
|
||||||
|
return InvalidNameServerTypeString
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToNameServerType returns a nameserver type
|
||||||
|
func ToNameServerType(typeString string) NameServerType {
|
||||||
|
switch typeString {
|
||||||
|
case UDPNameServerTypeString:
|
||||||
|
return UDPNameServerType
|
||||||
|
default:
|
||||||
|
return InvalidNameServerType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NameServerGroup group of nameservers and with group ids
|
||||||
|
type NameServerGroup struct {
|
||||||
|
// ID identifier of group
|
||||||
|
ID string
|
||||||
|
// Name group name
|
||||||
|
Name string
|
||||||
|
// Description group description
|
||||||
|
Description string
|
||||||
|
// NameServers list of nameservers
|
||||||
|
NameServers []NameServer
|
||||||
|
// Groups list of peer group IDs to distribute the nameservers information
|
||||||
|
Groups []string
|
||||||
|
// Primary indicates that the nameserver group is the primary resolver for any dns query
|
||||||
|
Primary bool
|
||||||
|
// Domains indicate the dns query domains to use with this nameserver group
|
||||||
|
Domains []string
|
||||||
|
// Enabled group status
|
||||||
|
Enabled bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NameServer represents a DNS nameserver
|
||||||
|
type NameServer struct {
|
||||||
|
// IP address of nameserver
|
||||||
|
IP netip.Addr
|
||||||
|
// NSType nameserver type
|
||||||
|
NSType NameServerType
|
||||||
|
// Port nameserver listening port
|
||||||
|
Port int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy copies a nameserver object
|
||||||
|
func (n *NameServer) Copy() *NameServer {
|
||||||
|
return &NameServer{
|
||||||
|
IP: n.IP,
|
||||||
|
NSType: n.NSType,
|
||||||
|
Port: n.Port,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsEqual compares one nameserver with the other
|
||||||
|
func (n *NameServer) IsEqual(other *NameServer) bool {
|
||||||
|
return other.IP == n.IP &&
|
||||||
|
other.NSType == n.NSType &&
|
||||||
|
other.Port == n.Port
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseNameServerURL parses a nameserver url in the format <type>://<ip>:<port>, e.g., udp://1.1.1.1:53
|
||||||
|
func ParseNameServerURL(nsURL string) (NameServer, error) {
|
||||||
|
parsedURL, err := url.Parse(nsURL)
|
||||||
|
if err != nil {
|
||||||
|
return NameServer{}, err
|
||||||
|
}
|
||||||
|
var ns NameServer
|
||||||
|
parsedScheme := strings.ToLower(parsedURL.Scheme)
|
||||||
|
nsType := ToNameServerType(parsedScheme)
|
||||||
|
if nsType == InvalidNameServerType {
|
||||||
|
return NameServer{}, fmt.Errorf("invalid nameserver url schema type, got %s", parsedScheme)
|
||||||
|
}
|
||||||
|
ns.NSType = nsType
|
||||||
|
|
||||||
|
parsedPort, err := strconv.Atoi(parsedURL.Port())
|
||||||
|
if err != nil {
|
||||||
|
return NameServer{}, fmt.Errorf("invalid nameserver url port, got %s", parsedURL.Port())
|
||||||
|
}
|
||||||
|
ns.Port = parsedPort
|
||||||
|
|
||||||
|
parsedAddr, err := netip.ParseAddr(parsedURL.Hostname())
|
||||||
|
if err != nil {
|
||||||
|
return NameServer{}, fmt.Errorf("invalid nameserver url IP, got %s", parsedURL.Hostname())
|
||||||
|
}
|
||||||
|
|
||||||
|
ns.IP = parsedAddr
|
||||||
|
|
||||||
|
return ns, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy copies a nameserver group object
|
||||||
|
func (g *NameServerGroup) Copy() *NameServerGroup {
|
||||||
|
return &NameServerGroup{
|
||||||
|
ID: g.ID,
|
||||||
|
Name: g.Name,
|
||||||
|
Description: g.Description,
|
||||||
|
NameServers: g.NameServers,
|
||||||
|
Groups: g.Groups,
|
||||||
|
Enabled: g.Enabled,
|
||||||
|
Primary: g.Primary,
|
||||||
|
Domains: g.Domains,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsEqual compares one nameserver group with the other
|
||||||
|
func (g *NameServerGroup) IsEqual(other *NameServerGroup) bool {
|
||||||
|
return other.ID == g.ID &&
|
||||||
|
other.Name == g.Name &&
|
||||||
|
other.Description == g.Description &&
|
||||||
|
other.Primary == g.Primary &&
|
||||||
|
compareNameServerList(g.NameServers, other.NameServers) &&
|
||||||
|
compareGroupsList(g.Groups, other.Groups) &&
|
||||||
|
compareGroupsList(g.Domains, other.Domains)
|
||||||
|
}
|
||||||
|
|
||||||
|
func compareNameServerList(list, other []NameServer) bool {
|
||||||
|
if len(list) != len(other) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ns := range list {
|
||||||
|
if !containsNameServer(ns, other) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func containsNameServer(element NameServer, list []NameServer) bool {
|
||||||
|
for _, ns := range list {
|
||||||
|
if ns.IsEqual(&element) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func compareGroupsList(list, other []string) bool {
|
||||||
|
if len(list) != len(other) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, id := range list {
|
||||||
|
match := false
|
||||||
|
for _, otherID := range other {
|
||||||
|
if id == otherID {
|
||||||
|
match = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !match {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
47
go.mod
47
go.mod
@@ -11,19 +11,19 @@ require (
|
|||||||
github.com/kardianos/service v1.2.1-0.20210728001519-a323c3813bc7 //keep this version otherwise wiretrustee up command breaks
|
github.com/kardianos/service v1.2.1-0.20210728001519-a323c3813bc7 //keep this version otherwise wiretrustee up command breaks
|
||||||
github.com/onsi/ginkgo v1.16.5
|
github.com/onsi/ginkgo v1.16.5
|
||||||
github.com/onsi/gomega v1.18.1
|
github.com/onsi/gomega v1.18.1
|
||||||
github.com/pion/ice/v2 v2.1.17
|
github.com/pion/ice/v2 v2.2.7
|
||||||
github.com/rs/cors v1.8.0
|
github.com/rs/cors v1.8.0
|
||||||
github.com/sirupsen/logrus v1.8.1
|
github.com/sirupsen/logrus v1.8.1
|
||||||
github.com/spf13/cobra v1.3.0
|
github.com/spf13/cobra v1.3.0
|
||||||
github.com/spf13/pflag v1.0.5
|
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-20220513210258-46612604a0f9
|
golang.org/x/crypto v0.0.0-20220513210258-46612604a0f9
|
||||||
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664
|
golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20211209221555-9c9e7e272434
|
golang.zx2c4.com/wireguard v0.0.0-20211209221555-9c9e7e272434
|
||||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20211215182854-7a385b3431de
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20211215182854-7a385b3431de
|
||||||
golang.zx2c4.com/wireguard/windows v0.5.1
|
golang.zx2c4.com/wireguard/windows v0.5.1
|
||||||
google.golang.org/grpc v1.43.0
|
google.golang.org/grpc v1.43.0
|
||||||
google.golang.org/protobuf v1.28.0
|
google.golang.org/protobuf v1.28.1
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -32,17 +32,24 @@ require (
|
|||||||
github.com/c-robinson/iplib v1.0.3
|
github.com/c-robinson/iplib v1.0.3
|
||||||
github.com/coreos/go-iptables v0.6.0
|
github.com/coreos/go-iptables v0.6.0
|
||||||
github.com/creack/pty v1.1.18
|
github.com/creack/pty v1.1.18
|
||||||
github.com/eko/gocache/v2 v2.3.1
|
github.com/eko/gocache/v3 v3.1.1
|
||||||
github.com/getlantern/systray v1.2.1
|
github.com/getlantern/systray v1.2.1
|
||||||
github.com/gliderlabs/ssh v0.3.4
|
github.com/gliderlabs/ssh v0.3.4
|
||||||
|
github.com/godbus/dbus/v5 v5.1.0
|
||||||
github.com/google/nftables v0.0.0-20220808154552-2eca00135732
|
github.com/google/nftables v0.0.0-20220808154552-2eca00135732
|
||||||
|
github.com/hashicorp/go-version v1.6.0
|
||||||
github.com/libp2p/go-netroute v0.2.0
|
github.com/libp2p/go-netroute v0.2.0
|
||||||
github.com/magiconair/properties v1.8.5
|
github.com/magiconair/properties v1.8.5
|
||||||
|
github.com/miekg/dns v1.1.41
|
||||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||||
|
github.com/prometheus/client_golang v1.13.0
|
||||||
github.com/rs/xid v1.3.0
|
github.com/rs/xid v1.3.0
|
||||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
|
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
|
||||||
github.com/stretchr/testify v1.7.1
|
github.com/stretchr/testify v1.8.0
|
||||||
golang.org/x/net v0.0.0-20220513224357-95641704303c
|
go.opentelemetry.io/otel/exporters/prometheus v0.33.0
|
||||||
|
go.opentelemetry.io/otel/metric v0.33.0
|
||||||
|
go.opentelemetry.io/otel/sdk/metric v0.33.0
|
||||||
|
golang.org/x/net v0.0.0-20220630215102-69896b714898
|
||||||
golang.org/x/term v0.0.0-20220526004731-065cf7ba2467
|
golang.org/x/term v0.0.0-20220526004731-065cf7ba2467
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -65,11 +72,12 @@ require (
|
|||||||
github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f // indirect
|
github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f // indirect
|
||||||
github.com/go-gl/gl v0.0.0-20210813123233-e4099ee2221f // indirect
|
github.com/go-gl/gl v0.0.0-20210813123233-e4099ee2221f // indirect
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211024062804-40e447a793be // indirect
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211024062804-40e447a793be // indirect
|
||||||
|
github.com/go-logr/logr v1.2.3 // indirect
|
||||||
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
github.com/go-redis/redis/v8 v8.11.5 // indirect
|
github.com/go-redis/redis/v8 v8.11.5 // indirect
|
||||||
github.com/go-stack/stack v1.8.0 // indirect
|
github.com/go-stack/stack v1.8.0 // indirect
|
||||||
github.com/godbus/dbus/v5 v5.0.4 // indirect
|
|
||||||
github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff // indirect
|
github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff // indirect
|
||||||
github.com/google/go-cmp v0.5.7 // indirect
|
github.com/google/go-cmp v0.5.9 // indirect
|
||||||
github.com/google/gopacket v1.1.19 // indirect
|
github.com/google/gopacket v1.1.19 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||||
github.com/josharian/native v0.0.0-20200817173448-b6b71def0850 // indirect
|
github.com/josharian/native v0.0.0-20200817173448-b6b71def0850 // indirect
|
||||||
@@ -80,28 +88,31 @@ require (
|
|||||||
github.com/nxadm/tail v1.4.8 // indirect
|
github.com/nxadm/tail v1.4.8 // indirect
|
||||||
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c // indirect
|
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c // indirect
|
||||||
github.com/pegasus-kv/thrift v0.13.0 // indirect
|
github.com/pegasus-kv/thrift v0.13.0 // indirect
|
||||||
github.com/pion/dtls/v2 v2.1.2 // indirect
|
github.com/pion/dtls/v2 v2.1.5 // indirect
|
||||||
github.com/pion/logging v0.2.2 // indirect
|
github.com/pion/logging v0.2.2 // indirect
|
||||||
github.com/pion/mdns v0.0.5 // indirect
|
github.com/pion/mdns v0.0.5 // indirect
|
||||||
github.com/pion/randutil v0.1.0 // indirect
|
github.com/pion/randutil v0.1.0 // indirect
|
||||||
github.com/pion/stun v0.3.5 // indirect
|
github.com/pion/stun v0.3.5 // indirect
|
||||||
github.com/pion/transport v0.13.0 // indirect
|
github.com/pion/transport v0.13.1 // indirect
|
||||||
github.com/pion/turn/v2 v2.0.7 // indirect
|
github.com/pion/turn/v2 v2.0.8 // indirect
|
||||||
github.com/pion/udp v0.1.1 // indirect
|
github.com/pion/udp v0.1.1 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/prometheus/client_golang v1.12.2 // indirect
|
|
||||||
github.com/prometheus/client_model v0.2.0 // indirect
|
github.com/prometheus/client_model v0.2.0 // indirect
|
||||||
github.com/prometheus/common v0.33.0 // indirect
|
github.com/prometheus/common v0.37.0 // indirect
|
||||||
github.com/prometheus/procfs v0.7.3 // indirect
|
github.com/prometheus/procfs v0.8.0 // indirect
|
||||||
github.com/rogpeppe/go-internal v1.8.0 // indirect
|
github.com/rogpeppe/go-internal v1.8.0 // indirect
|
||||||
github.com/spf13/cast v1.5.0 // indirect
|
github.com/spf13/cast v1.5.0 // indirect
|
||||||
github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564 // indirect
|
github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564 // indirect
|
||||||
github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9 // indirect
|
github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9 // indirect
|
||||||
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df // indirect
|
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df // indirect
|
||||||
github.com/yuin/goldmark v1.4.1 // indirect
|
github.com/yuin/goldmark v1.4.1 // indirect
|
||||||
|
go.opentelemetry.io/otel v1.11.1 // indirect
|
||||||
|
go.opentelemetry.io/otel/sdk v1.11.1 // indirect
|
||||||
|
go.opentelemetry.io/otel/trace v1.11.1 // indirect
|
||||||
|
golang.org/x/exp v0.0.0-20220518171630-0b5c67f07fdf // indirect
|
||||||
golang.org/x/image v0.0.0-20200430140353-33d19683fad8 // indirect
|
golang.org/x/image v0.0.0-20200430140353-33d19683fad8 // indirect
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
|
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
|
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f // indirect
|
||||||
golang.org/x/text v0.3.8-0.20211105212822-18b340fc7af2 // indirect
|
golang.org/x/text v0.3.8-0.20211105212822-18b340fc7af2 // indirect
|
||||||
golang.org/x/tools v0.1.10 // indirect
|
golang.org/x/tools v0.1.10 // indirect
|
||||||
golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f // indirect
|
golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f // indirect
|
||||||
@@ -112,11 +123,11 @@ require (
|
|||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
||||||
gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637 // indirect
|
gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
honnef.co/go/tools v0.2.2 // indirect
|
honnef.co/go/tools v0.2.2 // indirect
|
||||||
k8s.io/apimachinery v0.23.5 // indirect
|
k8s.io/apimachinery v0.23.5 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
replace github.com/pion/ice/v2 => github.com/wiretrustee/ice/v2 v2.1.21-0.20220218121004-dc81faead4bb
|
|
||||||
|
|
||||||
replace github.com/kardianos/service => github.com/netbirdio/service v0.0.0-20220905002524-6ac14ad5ea84
|
replace github.com/kardianos/service => github.com/netbirdio/service v0.0.0-20220905002524-6ac14ad5ea84
|
||||||
|
|
||||||
|
replace github.com/getlantern/systray => github.com/netbirdio/systray v0.0.0-20221012095658-dc8eda872c0c
|
||||||
|
|||||||
91
go.sum
91
go.sum
@@ -134,8 +134,8 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cu
|
|||||||
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
|
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
|
||||||
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
|
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
|
||||||
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
|
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
|
||||||
github.com/eko/gocache/v2 v2.3.1 h1:8MMkfqGJ0KIA9OXT0rXevcEIrU16oghrGDiIDJDFCa0=
|
github.com/eko/gocache/v3 v3.1.1 h1:r3CBwLnqPkcK56h9Do2CWw1kZ4TeKK0wDE1Oo/YZnhs=
|
||||||
github.com/eko/gocache/v2 v2.3.1/go.mod h1:l2z8OmpZHL0CpuzDJtxm267eF3mZW1NqUsMj+sKrbUs=
|
github.com/eko/gocache/v3 v3.1.1/go.mod h1:UpP/LyHAioP/a/dizgl0MpgZ3A3CkS4NbG/mWkGTQ9M=
|
||||||
github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
|
github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
|
||||||
github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
|
github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
|
||||||
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
|
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
|
||||||
@@ -178,8 +178,6 @@ github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55 h1:XYzSdCbkzOC0F
|
|||||||
github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55/go.mod h1:6mmzY2kW1TOOrVy+r41Za2MxXM+hhqTtY3oBKd2AgFA=
|
github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55/go.mod h1:6mmzY2kW1TOOrVy+r41Za2MxXM+hhqTtY3oBKd2AgFA=
|
||||||
github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f h1:wrYrQttPS8FHIRSlsrcuKazukx/xqO/PpLZzZXsF+EA=
|
github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f h1:wrYrQttPS8FHIRSlsrcuKazukx/xqO/PpLZzZXsF+EA=
|
||||||
github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f/go.mod h1:D5ao98qkA6pxftxoqzibIBBrLSUli+kYnJqrgBf9cIA=
|
github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f/go.mod h1:D5ao98qkA6pxftxoqzibIBBrLSUli+kYnJqrgBf9cIA=
|
||||||
github.com/getlantern/systray v1.2.1 h1:udsC2k98v2hN359VTFShuQW6GGprRprw6kD6539JikI=
|
|
||||||
github.com/getlantern/systray v1.2.1/go.mod h1:AecygODWIsBquJCJFop8MEQcJbWFfw/1yWbVabNgpCM=
|
|
||||||
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||||
@@ -204,6 +202,11 @@ github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KE
|
|||||||
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
|
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
|
||||||
github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
|
github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
|
||||||
github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||||
|
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||||
|
github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
|
||||||
|
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||||
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||||
github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0=
|
github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0=
|
||||||
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||||
@@ -220,8 +223,9 @@ github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq
|
|||||||
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
|
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
|
||||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||||
github.com/godbus/dbus/v5 v5.0.4 h1:9349emZab16e7zQvpmsbtjc18ykshndd8y2PG3sgJbA=
|
|
||||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
|
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
|
||||||
|
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||||
@@ -280,8 +284,8 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
|||||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
|
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||||
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
|
github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
@@ -344,6 +348,8 @@ github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerX
|
|||||||
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
||||||
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||||
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||||
|
github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek=
|
||||||
|
github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||||
@@ -452,6 +458,7 @@ github.com/mdlayher/socket v0.0.0-20211102153432-57e3fa563ecb h1:2dC7L10LmTqlyMV
|
|||||||
github.com/mdlayher/socket v0.0.0-20211102153432-57e3fa563ecb/go.mod h1:nFZ1EtZYK8Gi/k6QNu7z7CgO20i/4ExeQswwWuPmG/g=
|
github.com/mdlayher/socket v0.0.0-20211102153432-57e3fa563ecb/go.mod h1:nFZ1EtZYK8Gi/k6QNu7z7CgO20i/4ExeQswwWuPmG/g=
|
||||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||||
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
|
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
|
||||||
|
github.com/miekg/dns v1.1.41 h1:WMszZWJG0XmzbK9FEmzH2TVcqYzFesusSIB41b8KHxY=
|
||||||
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
|
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
|
||||||
github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721 h1:RlZweED6sbSArvlE924+mUcZuXKLBHA35U7LN621Bws=
|
github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721 h1:RlZweED6sbSArvlE924+mUcZuXKLBHA35U7LN621Bws=
|
||||||
github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721/go.mod h1:Ickgr2WtCLZ2MDGd4Gr0geeCH5HybhRJbonOgQpvSxc=
|
github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721/go.mod h1:Ickgr2WtCLZ2MDGd4Gr0geeCH5HybhRJbonOgQpvSxc=
|
||||||
@@ -474,6 +481,8 @@ github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRW
|
|||||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
|
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
|
||||||
github.com/netbirdio/service v0.0.0-20220905002524-6ac14ad5ea84 h1:u8kpzR9ld1uAeH/BAXsS0SfcnhooLWeO7UgHSBVPD9I=
|
github.com/netbirdio/service v0.0.0-20220905002524-6ac14ad5ea84 h1:u8kpzR9ld1uAeH/BAXsS0SfcnhooLWeO7UgHSBVPD9I=
|
||||||
github.com/netbirdio/service v0.0.0-20220905002524-6ac14ad5ea84/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM=
|
github.com/netbirdio/service v0.0.0-20220905002524-6ac14ad5ea84/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM=
|
||||||
|
github.com/netbirdio/systray v0.0.0-20221012095658-dc8eda872c0c h1:wK/s4nyZj/GF/kFJQjX6nqNfE0G3gcqd6hhnPCyp4sw=
|
||||||
|
github.com/netbirdio/systray v0.0.0-20221012095658-dc8eda872c0c/go.mod h1:AecygODWIsBquJCJFop8MEQcJbWFfw/1yWbVabNgpCM=
|
||||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||||
@@ -505,8 +514,10 @@ github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTK
|
|||||||
github.com/pegasus-kv/thrift v0.13.0 h1:4ESwaNoHImfbHa9RUGJiJZ4hrxorihZHk5aarYwY8d4=
|
github.com/pegasus-kv/thrift v0.13.0 h1:4ESwaNoHImfbHa9RUGJiJZ4hrxorihZHk5aarYwY8d4=
|
||||||
github.com/pegasus-kv/thrift v0.13.0/go.mod h1:Gl9NT/WHG6ABm6NsrbfE8LiJN0sAyneCrvB4qN4NPqQ=
|
github.com/pegasus-kv/thrift v0.13.0/go.mod h1:Gl9NT/WHG6ABm6NsrbfE8LiJN0sAyneCrvB4qN4NPqQ=
|
||||||
github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||||
github.com/pion/dtls/v2 v2.1.2 h1:22Q1Jk9L++Yo7BIf9130MonNPfPVb+YgdYLeyQotuAA=
|
github.com/pion/dtls/v2 v2.1.5 h1:jlh2vtIyUBShchoTDqpCCqiYCyRFJ/lvf/gQ8TALs+c=
|
||||||
github.com/pion/dtls/v2 v2.1.2/go.mod h1:o6+WvyLDAlXF7YiPB/RlskRoeK+/JtuaZa5emwQcWus=
|
github.com/pion/dtls/v2 v2.1.5/go.mod h1:BqCE7xPZbPSubGasRoDFJeTsyJtdD1FanJYL0JGheqY=
|
||||||
|
github.com/pion/ice/v2 v2.2.7 h1:kG9tux3WdYUSqqqnf+O5zKlpy41PdlvLUBlYJeV2emQ=
|
||||||
|
github.com/pion/ice/v2 v2.2.7/go.mod h1:Ckj7cWZ717rtU01YoDQA9ntGWCk95D42uVZ8sI0EL+8=
|
||||||
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
|
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
|
||||||
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
|
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
|
||||||
github.com/pion/mdns v0.0.5 h1:Q2oj/JB3NqfzY9xGZ1fPzZzK7sDSD8rZPOvcIQ10BCw=
|
github.com/pion/mdns v0.0.5 h1:Q2oj/JB3NqfzY9xGZ1fPzZzK7sDSD8rZPOvcIQ10BCw=
|
||||||
@@ -516,10 +527,11 @@ github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TB
|
|||||||
github.com/pion/stun v0.3.5 h1:uLUCBCkQby4S1cf6CGuR9QrVOKcvUwFeemaC865QHDg=
|
github.com/pion/stun v0.3.5 h1:uLUCBCkQby4S1cf6CGuR9QrVOKcvUwFeemaC865QHDg=
|
||||||
github.com/pion/stun v0.3.5/go.mod h1:gDMim+47EeEtfWogA37n6qXZS88L5V6LqFcf+DZA2UA=
|
github.com/pion/stun v0.3.5/go.mod h1:gDMim+47EeEtfWogA37n6qXZS88L5V6LqFcf+DZA2UA=
|
||||||
github.com/pion/transport v0.12.2/go.mod h1:N3+vZQD9HlDP5GWkZ85LohxNsDcNgofQmyL6ojX5d8Q=
|
github.com/pion/transport v0.12.2/go.mod h1:N3+vZQD9HlDP5GWkZ85LohxNsDcNgofQmyL6ojX5d8Q=
|
||||||
github.com/pion/transport v0.13.0 h1:KWTA5ZrQogizzYwPEciGtHPLwpAjE91FgXnyu+Hv2uY=
|
|
||||||
github.com/pion/transport v0.13.0/go.mod h1:yxm9uXpK9bpBBWkITk13cLo1y5/ur5VQpG22ny6EP7g=
|
github.com/pion/transport v0.13.0/go.mod h1:yxm9uXpK9bpBBWkITk13cLo1y5/ur5VQpG22ny6EP7g=
|
||||||
github.com/pion/turn/v2 v2.0.7 h1:SZhc00WDovK6czaN1RSiHqbwANtIO6wfZQsU0m0KNE8=
|
github.com/pion/transport v0.13.1 h1:/UH5yLeQtwm2VZIPjxwnNFxjS4DFhyLfS4GlfuKUzfA=
|
||||||
github.com/pion/turn/v2 v2.0.7/go.mod h1:+y7xl719J8bAEVpSXBXvTxStjJv3hbz9YFflvkpcGPw=
|
github.com/pion/transport v0.13.1/go.mod h1:EBxbqzyv+ZrmDb82XswEE0BjfQFtuw1Nu6sjnjWCsGg=
|
||||||
|
github.com/pion/turn/v2 v2.0.8 h1:KEstL92OUN3k5k8qxsXHpr7WWfrdp7iJZHx99ud8muw=
|
||||||
|
github.com/pion/turn/v2 v2.0.8/go.mod h1:+y7xl719J8bAEVpSXBXvTxStjJv3hbz9YFflvkpcGPw=
|
||||||
github.com/pion/udp v0.1.1 h1:8UAPvyqmsxK8oOjloDk4wUt63TzFe9WEJkg5lChlj7o=
|
github.com/pion/udp v0.1.1 h1:8UAPvyqmsxK8oOjloDk4wUt63TzFe9WEJkg5lChlj7o=
|
||||||
github.com/pion/udp v0.1.1/go.mod h1:6AFo+CMdKQm7UiA0eUPA8/eVCTx8jBIITLZHc9DWX5M=
|
github.com/pion/udp v0.1.1/go.mod h1:6AFo+CMdKQm7UiA0eUPA8/eVCTx8jBIITLZHc9DWX5M=
|
||||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||||
@@ -539,8 +551,8 @@ github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3O
|
|||||||
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
||||||
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
|
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
|
||||||
github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
|
github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
|
||||||
github.com/prometheus/client_golang v1.12.2 h1:51L9cDoUHVrXx4zWYlcLQIZ+d+VXHgqnYKkIuq4g/34=
|
github.com/prometheus/client_golang v1.13.0 h1:b71QUfeo5M8gq2+evJdTPfZhYMAU0uKPkyPJ7TPsloU=
|
||||||
github.com/prometheus/client_golang v1.12.2/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
|
github.com/prometheus/client_golang v1.13.0/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5nJFOEMgftOUOmlvYQ=
|
||||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
@@ -551,15 +563,16 @@ github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8b
|
|||||||
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
|
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
|
||||||
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
|
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
|
||||||
github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
|
github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
|
||||||
github.com/prometheus/common v0.33.0 h1:rHgav/0a6+uYgGdNt3jwz8FNSesO/Hsang3O0T9A5SE=
|
github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE=
|
||||||
github.com/prometheus/common v0.33.0/go.mod h1:gB3sOl7P0TvJabZpLY5uQMpUqRCPPCyRLCZYc7JZTNE=
|
github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA=
|
||||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||||
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
|
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
|
||||||
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||||
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||||
github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU=
|
|
||||||
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||||
|
github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo=
|
||||||
|
github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4=
|
||||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
|
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
|
||||||
@@ -606,6 +619,7 @@ github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9/go.mod h1:mvWM0+15
|
|||||||
github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
|
github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
@@ -613,8 +627,9 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
|
|||||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
|
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||||
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||||
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
|
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
|
||||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||||
@@ -624,8 +639,6 @@ github.com/vishvananda/netlink v1.1.0 h1:1iyaYNBLmP6L0220aDnYQpo1QEV4t4hJ+xEEhhJ
|
|||||||
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
|
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
|
||||||
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df h1:OviZH7qLw/7ZovXvuNyL3XQl8UFofeikI1NW1Gypu7k=
|
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df h1:OviZH7qLw/7ZovXvuNyL3XQl8UFofeikI1NW1Gypu7k=
|
||||||
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
|
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
|
||||||
github.com/wiretrustee/ice/v2 v2.1.21-0.20220218121004-dc81faead4bb h1:CU1/+CEeCPvYXgfAyqTJXSQSf6hW3wsWM6Dfz6HkHEQ=
|
|
||||||
github.com/wiretrustee/ice/v2 v2.1.21-0.20220218121004-dc81faead4bb/go.mod h1:XT1Nrb4OxbVFPffbQMbq4PaeEkpRLVzdphh3fjrw7DY=
|
|
||||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
@@ -645,6 +658,18 @@ go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
|||||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
||||||
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
|
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
|
||||||
|
go.opentelemetry.io/otel v1.11.1 h1:4WLLAmcfkmDk2ukNXJyq3/kiz/3UzCaYq6PskJsaou4=
|
||||||
|
go.opentelemetry.io/otel v1.11.1/go.mod h1:1nNhXBbWSD0nsL38H6btgnFN2k4i0sNLHNNMZMSbUGE=
|
||||||
|
go.opentelemetry.io/otel/exporters/prometheus v0.33.0 h1:xXhPj7SLKWU5/Zd4Hxmd+X1C4jdmvc0Xy+kvjFx2z60=
|
||||||
|
go.opentelemetry.io/otel/exporters/prometheus v0.33.0/go.mod h1:ZSmYfKdYWEdSDBB4njLBIwTf4AU2JNsH3n2quVQDebI=
|
||||||
|
go.opentelemetry.io/otel/metric v0.33.0 h1:xQAyl7uGEYvrLAiV/09iTJlp1pZnQ9Wl793qbVvED1E=
|
||||||
|
go.opentelemetry.io/otel/metric v0.33.0/go.mod h1:QlTYc+EnYNq/M2mNk1qDDMRLpqCOj2f/r5c7Fd5FYaI=
|
||||||
|
go.opentelemetry.io/otel/sdk v1.11.1 h1:F7KmQgoHljhUuJyA+9BiU+EkJfyX5nVVF4wyzWZpKxs=
|
||||||
|
go.opentelemetry.io/otel/sdk v1.11.1/go.mod h1:/l3FE4SupHJ12TduVjUkZtlfFqDCQJlOlithYrdktys=
|
||||||
|
go.opentelemetry.io/otel/sdk/metric v0.33.0 h1:oTqyWfksgKoJmbrs2q7O7ahkJzt+Ipekihf8vhpa9qo=
|
||||||
|
go.opentelemetry.io/otel/sdk/metric v0.33.0/go.mod h1:xdypMeA21JBOvjjzDUtD0kzIcHO/SPez+a8HOzJPGp0=
|
||||||
|
go.opentelemetry.io/otel/trace v1.11.1 h1:ofxdnzsNrGBYXbP7t7zpUK281+go5rF7dvdIZXF8gdQ=
|
||||||
|
go.opentelemetry.io/otel/trace v1.11.1/go.mod h1:f/Q9G7vzk5u91PhbmKbg1Qn0rzH1LJ4vbPHFGkTPtOk=
|
||||||
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
||||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||||
@@ -662,7 +687,7 @@ golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5y
|
|||||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.0.0-20211202192323-5770296d904e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20211202192323-5770296d904e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/crypto v0.0.0-20220513210258-46612604a0f9 h1:NUzdAbFtCJSXU20AOXgeqaUwg8Ypg4MPYmL+d+rsB5c=
|
golang.org/x/crypto v0.0.0-20220513210258-46612604a0f9 h1:NUzdAbFtCJSXU20AOXgeqaUwg8Ypg4MPYmL+d+rsB5c=
|
||||||
golang.org/x/crypto v0.0.0-20220513210258-46612604a0f9/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20220513210258-46612604a0f9/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
@@ -675,6 +700,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
|
|||||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||||
|
golang.org/x/exp v0.0.0-20220518171630-0b5c67f07fdf h1:oXVg4h2qJDd9htKxb5SCpFBHLipW6hXmL3qpUixS2jw=
|
||||||
|
golang.org/x/exp v0.0.0-20220518171630-0b5c67f07fdf/go.mod h1:yh0Ynu2b5ZUe3MQfp2nM0ecK7wsgouWTDN0FNeJuIys=
|
||||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
golang.org/x/image v0.0.0-20200430140353-33d19683fad8 h1:6WW6V3x1P/jokJBpRQYUJnMHRP6isStQwCozxnU7XQw=
|
golang.org/x/image v0.0.0-20200430140353-33d19683fad8 h1:6WW6V3x1P/jokJBpRQYUJnMHRP6isStQwCozxnU7XQw=
|
||||||
@@ -772,8 +799,10 @@ golang.org/x/net v0.0.0-20211208012354-db4efeb81f4b/go.mod h1:9nx3DQGgdP8bBQD5qx
|
|||||||
golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||||
golang.org/x/net v0.0.0-20220513224357-95641704303c h1:nF9mHSvoKBLkQNQhJZNsc66z2UzAMUbLGjC95CF3pU0=
|
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||||
golang.org/x/net v0.0.0-20220513224357-95641704303c/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
golang.org/x/net v0.0.0-20220531201128-c960675eff93/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
|
golang.org/x/net v0.0.0-20220630215102-69896b714898 h1:K7wO6V1IrczY9QOQ2WkVpw4JQSwCd52UsxVEirZUfiw=
|
||||||
|
golang.org/x/net v0.0.0-20220630215102-69896b714898/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
@@ -802,8 +831,9 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ
|
|||||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f h1:Ax0t5p6N38Ga0dThY21weqDEyz2oklo4IvDkpigvkD8=
|
||||||
|
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
@@ -906,8 +936,10 @@ golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.0.0-20211214234402-4825e8c3871d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211214234402-4825e8c3871d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664 h1:wEZYwx+kK+KlZ0hpvP2Ls1Xr4+RWnlzGFwPP0aiDjIU=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220608164250-635b8c9b7f68/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8 h1:h+EGohizhe9XlX18rfpa8k8RAc5XyaeamM+0VHRd4lc=
|
||||||
|
golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 h1:CBpWXWQpIRjzmkkA+M7q9Fqnwd2mZr3AFqexg8YTfoM=
|
golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 h1:CBpWXWQpIRjzmkkA+M7q9Fqnwd2mZr3AFqexg8YTfoM=
|
||||||
@@ -1153,8 +1185,8 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba
|
|||||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
|
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
|
||||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
@@ -1185,8 +1217,9 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
|||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
|||||||
@@ -73,13 +73,15 @@ func parseAddress(address string) (WGAddress, error) {
|
|||||||
func (w *WGIface) Close() error {
|
func (w *WGIface) Close() error {
|
||||||
w.mu.Lock()
|
w.mu.Lock()
|
||||||
defer w.mu.Unlock()
|
defer w.mu.Unlock()
|
||||||
|
if w.Interface == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
err := w.Interface.Close()
|
err := w.Interface.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if runtime.GOOS == "darwin" {
|
if runtime.GOOS != "windows" {
|
||||||
sockPath := "/var/run/wireguard/" + w.Name + ".sock"
|
sockPath := "/var/run/wireguard/" + w.Name + ".sock"
|
||||||
if _, statErr := os.Stat(sockPath); statErr == nil {
|
if _, statErr := os.Stat(sockPath); statErr == nil {
|
||||||
statErr = os.Remove(sockPath)
|
statErr = os.Remove(sockPath)
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ func (w *WGIface) assignAddr() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// WireguardModExists check if we can load wireguard mod (linux only)
|
// WireguardModuleIsLoaded check if we can load wireguard mod (linux only)
|
||||||
func WireguardModExists() bool {
|
func WireguardModuleIsLoaded() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,48 +1,29 @@
|
|||||||
package iface
|
package iface
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"fmt"
|
||||||
"math"
|
|
||||||
"os"
|
|
||||||
"syscall"
|
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/vishvananda/netlink"
|
"github.com/vishvananda/netlink"
|
||||||
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
type NativeLink struct {
|
type NativeLink struct {
|
||||||
Link *netlink.Link
|
Link *netlink.Link
|
||||||
}
|
}
|
||||||
|
|
||||||
// WireguardModExists check if we can load wireguard mod (linux only)
|
|
||||||
func WireguardModExists() bool {
|
|
||||||
link := newWGLink("mustnotexist")
|
|
||||||
|
|
||||||
// We willingly try to create a device with an invalid
|
|
||||||
// MTU here as the validation of the MTU will be performed after
|
|
||||||
// the validation of the link kind and hence allows us to check
|
|
||||||
// for the existance of the wireguard module without actually
|
|
||||||
// creating a link.
|
|
||||||
//
|
|
||||||
// As a side-effect, this will also let the kernel lazy-load
|
|
||||||
// the wireguard module.
|
|
||||||
link.attrs.MTU = math.MaxInt
|
|
||||||
|
|
||||||
err := netlink.LinkAdd(link)
|
|
||||||
|
|
||||||
return errors.Is(err, syscall.EINVAL)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create creates a new Wireguard interface, sets a given IP and brings it up.
|
// Create creates a new Wireguard interface, sets a given IP and brings it up.
|
||||||
// Will reuse an existing one.
|
// Will reuse an existing one.
|
||||||
func (w *WGIface) Create() error {
|
func (w *WGIface) Create() error {
|
||||||
w.mu.Lock()
|
w.mu.Lock()
|
||||||
defer w.mu.Unlock()
|
defer w.mu.Unlock()
|
||||||
|
|
||||||
if WireguardModExists() {
|
if WireguardModuleIsLoaded() {
|
||||||
log.Info("using kernel WireGuard")
|
log.Info("using kernel WireGuard")
|
||||||
return w.createWithKernel()
|
return w.createWithKernel()
|
||||||
} else {
|
} else {
|
||||||
|
if !tunModuleIsLoaded() {
|
||||||
|
return fmt.Errorf("couldn't check or load tun module")
|
||||||
|
}
|
||||||
log.Info("using userspace WireGuard")
|
log.Info("using userspace WireGuard")
|
||||||
return w.createWithUserspace()
|
return w.createWithUserspace()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,7 +89,6 @@ func getIfaceAddrs(ifaceName string) ([]net.Addr, error) {
|
|||||||
return addrs, nil
|
return addrs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
|
||||||
func Test_CreateInterface(t *testing.T) {
|
func Test_CreateInterface(t *testing.T) {
|
||||||
ifaceName := fmt.Sprintf("utun%d", WgIntNumber+1)
|
ifaceName := fmt.Sprintf("utun%d", WgIntNumber+1)
|
||||||
wgIP := "10.99.99.1/32"
|
wgIP := "10.99.99.1/32"
|
||||||
@@ -369,8 +368,8 @@ func Test_ConnectPeers(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
// todo: investigate why in some tests execution we need 30s
|
||||||
timeout := 10 * time.Second
|
timeout := 30 * time.Second
|
||||||
timeoutChannel := time.After(timeout)
|
timeoutChannel := time.After(timeout)
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
|
|||||||
@@ -75,3 +75,8 @@ func (w *WGIface) UpdateAddr(newAddr string) error {
|
|||||||
w.Address = addr
|
w.Address = addr
|
||||||
return w.assignAddr()
|
return w.assignAddr()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetInterfaceGUIDString returns an interface GUID. This is useful on Windows only
|
||||||
|
func (w *WGIface) GetInterfaceGUIDString() (string, error) {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -58,7 +58,21 @@ func (w *WGIface) UpdateAddr(newAddr string) error {
|
|||||||
return w.assignAddr(luid)
|
return w.assignAddr(luid)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WireguardModExists check if we can load wireguard mod (linux only)
|
// GetInterfaceGUIDString returns an interface GUID string
|
||||||
func WireguardModExists() bool {
|
func (w *WGIface) GetInterfaceGUIDString() (string, error) {
|
||||||
|
if w.Interface == nil {
|
||||||
|
return "", fmt.Errorf("interface has not been initialized yet")
|
||||||
|
}
|
||||||
|
windowsDevice := w.Interface.(*driver.Adapter)
|
||||||
|
luid := windowsDevice.LUID()
|
||||||
|
guid, err := luid.GUID()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return guid.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WireguardModuleIsLoaded check if we can load wireguard mod (linux only)
|
||||||
|
func WireguardModuleIsLoaded() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
350
iface/module_linux.go
Normal file
350
iface/module_linux.go
Normal file
@@ -0,0 +1,350 @@
|
|||||||
|
// Package iface provides wireguard network interface creation and management
|
||||||
|
package iface
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"github.com/vishvananda/netlink"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
"io/fs"
|
||||||
|
"io/ioutil"
|
||||||
|
"math"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Holds logic to check existence of kernel modules used by wireguard interfaces
|
||||||
|
// Copied from https://github.com/paultag/go-modprobe and
|
||||||
|
// https://github.com/pmorjan/kmod
|
||||||
|
|
||||||
|
type status int
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultModuleDir = "/lib/modules"
|
||||||
|
unknown status = iota
|
||||||
|
unloaded
|
||||||
|
unloading
|
||||||
|
loading
|
||||||
|
live
|
||||||
|
inuse
|
||||||
|
)
|
||||||
|
|
||||||
|
type module struct {
|
||||||
|
name string
|
||||||
|
path string
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrModuleNotFound is the error resulting if a module can't be found.
|
||||||
|
ErrModuleNotFound = errors.New("module not found")
|
||||||
|
moduleLibDir = defaultModuleDir
|
||||||
|
// get the root directory for the kernel modules. If this line panics,
|
||||||
|
// it's because getModuleRoot has failed to get the uname of the running
|
||||||
|
// kernel (likely a non-POSIX system, but maybe a broken kernel?)
|
||||||
|
moduleRoot = getModuleRoot()
|
||||||
|
)
|
||||||
|
|
||||||
|
// Get the module root (/lib/modules/$(uname -r)/)
|
||||||
|
func getModuleRoot() string {
|
||||||
|
uname := unix.Utsname{}
|
||||||
|
if err := unix.Uname(&uname); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
i := 0
|
||||||
|
for ; uname.Release[i] != 0; i++ {
|
||||||
|
}
|
||||||
|
|
||||||
|
return filepath.Join(moduleLibDir, string(uname.Release[:i]))
|
||||||
|
}
|
||||||
|
|
||||||
|
// tunModuleIsLoaded check if tun module exist, if is not attempt to load it
|
||||||
|
func tunModuleIsLoaded() bool {
|
||||||
|
_, err := os.Stat("/dev/net/tun")
|
||||||
|
if err == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("couldn't access device /dev/net/tun, go error %v, "+
|
||||||
|
"will attempt to load tun module, if running on container add flag --cap-add=NET_ADMIN", err)
|
||||||
|
|
||||||
|
tunLoaded, err := tryToLoadModule("tun")
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("unable to find or load tun module, got error: %v", err)
|
||||||
|
}
|
||||||
|
return tunLoaded
|
||||||
|
}
|
||||||
|
|
||||||
|
// WireguardModuleIsLoaded check if we can load wireguard mod (linux only)
|
||||||
|
func WireguardModuleIsLoaded() bool {
|
||||||
|
if canCreateFakeWireguardInterface() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
loaded, err := tryToLoadModule("wireguard")
|
||||||
|
if err != nil {
|
||||||
|
log.Info(err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return loaded
|
||||||
|
}
|
||||||
|
|
||||||
|
func canCreateFakeWireguardInterface() bool {
|
||||||
|
link := newWGLink("mustnotexist")
|
||||||
|
|
||||||
|
// We willingly try to create a device with an invalid
|
||||||
|
// MTU here as the validation of the MTU will be performed after
|
||||||
|
// the validation of the link kind and hence allows us to check
|
||||||
|
// for the existance of the wireguard module without actually
|
||||||
|
// creating a link.
|
||||||
|
//
|
||||||
|
// As a side-effect, this will also let the kernel lazy-load
|
||||||
|
// the wireguard module.
|
||||||
|
link.attrs.MTU = math.MaxInt
|
||||||
|
|
||||||
|
err := netlink.LinkAdd(link)
|
||||||
|
|
||||||
|
return errors.Is(err, syscall.EINVAL)
|
||||||
|
}
|
||||||
|
|
||||||
|
func tryToLoadModule(moduleName string) (bool, error) {
|
||||||
|
if isModuleEnabled(moduleName) {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
modulePath, err := getModulePath(moduleName)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("couldn't find module path for %s, error: %v", moduleName, err)
|
||||||
|
}
|
||||||
|
if modulePath == "" {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("trying to load %s module", moduleName)
|
||||||
|
|
||||||
|
err = loadModuleWithDependencies(moduleName, modulePath)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("couldn't load %s module, error: %v", moduleName, err)
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isModuleEnabled(name string) bool {
|
||||||
|
builtin, builtinErr := isBuiltinModule(name)
|
||||||
|
state, statusErr := moduleStatus(name)
|
||||||
|
return (builtinErr == nil && builtin) || (statusErr == nil && state >= loading)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getModulePath(name string) (string, error) {
|
||||||
|
var foundPath string
|
||||||
|
skipRemainingDirs := false
|
||||||
|
|
||||||
|
err := filepath.WalkDir(
|
||||||
|
moduleRoot,
|
||||||
|
func(path string, info fs.DirEntry, err error) error {
|
||||||
|
if skipRemainingDirs {
|
||||||
|
return fs.SkipDir
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
// skip broken files
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !info.Type().IsRegular() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
nameFromPath := pathToName(path)
|
||||||
|
if nameFromPath == name {
|
||||||
|
foundPath = path
|
||||||
|
skipRemainingDirs = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return foundPath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func pathToName(s string) string {
|
||||||
|
s = filepath.Base(s)
|
||||||
|
for ext := filepath.Ext(s); ext != ""; ext = filepath.Ext(s) {
|
||||||
|
s = strings.TrimSuffix(s, ext)
|
||||||
|
}
|
||||||
|
return cleanName(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func cleanName(s string) string {
|
||||||
|
return strings.ReplaceAll(strings.TrimSpace(s), "-", "_")
|
||||||
|
}
|
||||||
|
|
||||||
|
func isBuiltinModule(name string) (bool, error) {
|
||||||
|
f, err := os.Open(filepath.Join(moduleRoot, "/modules.builtin"))
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
err := f.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed closing modules.builtin file, %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
var found bool
|
||||||
|
scanner := bufio.NewScanner(f)
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := scanner.Text()
|
||||||
|
if pathToName(line) == name {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return found, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// /proc/modules
|
||||||
|
//
|
||||||
|
// name | memory size | reference count | references | state: <Live|Loading|Unloading>
|
||||||
|
// macvlan 28672 1 macvtap, Live 0x0000000000000000
|
||||||
|
func moduleStatus(name string) (status, error) {
|
||||||
|
state := unknown
|
||||||
|
f, err := os.Open("/proc/modules")
|
||||||
|
if err != nil {
|
||||||
|
return state, err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
err := f.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed closing /proc/modules file, %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
state = unloaded
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(f)
|
||||||
|
for scanner.Scan() {
|
||||||
|
fields := strings.Fields(scanner.Text())
|
||||||
|
if fields[0] == name {
|
||||||
|
if fields[2] != "0" {
|
||||||
|
state = inuse
|
||||||
|
break
|
||||||
|
}
|
||||||
|
switch fields[4] {
|
||||||
|
case "Live":
|
||||||
|
state = live
|
||||||
|
case "Loading":
|
||||||
|
state = loading
|
||||||
|
case "Unloading":
|
||||||
|
state = unloading
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
return state, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return state, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadModuleWithDependencies(name, path string) error {
|
||||||
|
deps, err := getModuleDependencies(name)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("couldn't load list of module %s dependecies", name)
|
||||||
|
}
|
||||||
|
for _, dep := range deps {
|
||||||
|
err = loadModule(dep.name, dep.path)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("couldn't load dependecy module %s for %s", dep.name, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return loadModule(name, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadModule(name, path string) error {
|
||||||
|
state, err := moduleStatus(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if state >= loading {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
err := f.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed closing %s file, %v", path, err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// first try finit_module(2), then init_module(2)
|
||||||
|
err = unix.FinitModule(int(f.Fd()), "", 0)
|
||||||
|
if errors.Is(err, unix.ENOSYS) {
|
||||||
|
buf, err := ioutil.ReadAll(f)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return unix.InitModule(buf, "")
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// getModuleDependencies returns a module dependencies
|
||||||
|
func getModuleDependencies(name string) ([]module, error) {
|
||||||
|
f, err := os.Open(filepath.Join(moduleRoot, "/modules.dep"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
err := f.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed closing modules.dep file, %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
var deps []string
|
||||||
|
scanner := bufio.NewScanner(f)
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := scanner.Text()
|
||||||
|
fields := strings.Fields(line)
|
||||||
|
if pathToName(strings.TrimSuffix(fields[0], ":")) == name {
|
||||||
|
deps = fields
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(deps) == 0 {
|
||||||
|
return nil, ErrModuleNotFound
|
||||||
|
}
|
||||||
|
deps[0] = strings.TrimSuffix(deps[0], ":")
|
||||||
|
|
||||||
|
var modules []module
|
||||||
|
for _, v := range deps {
|
||||||
|
if pathToName(v) != name {
|
||||||
|
modules = append(modules, module{
|
||||||
|
name: pathToName(v),
|
||||||
|
path: filepath.Join(moduleRoot, v),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return modules, nil
|
||||||
|
}
|
||||||
221
iface/module_linux_test.go
Normal file
221
iface/module_linux_test.go
Normal file
@@ -0,0 +1,221 @@
|
|||||||
|
package iface
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetModuleDependencies(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
module string
|
||||||
|
expected []module
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Get Single Dependency",
|
||||||
|
module: "bar",
|
||||||
|
expected: []module{
|
||||||
|
{name: "foo", path: "kernel/a/foo.ko"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Get Multiple Dependencies",
|
||||||
|
module: "baz",
|
||||||
|
expected: []module{
|
||||||
|
{name: "foo", path: "kernel/a/foo.ko"},
|
||||||
|
{name: "bar", path: "kernel/a/bar.ko"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Get No Dependencies",
|
||||||
|
module: "foo",
|
||||||
|
expected: []module{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
|
defer resetGlobals()
|
||||||
|
_, _ = createFiles(t)
|
||||||
|
modules, err := getModuleDependencies(testCase.module)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
expected := testCase.expected
|
||||||
|
for i := range expected {
|
||||||
|
expected[i].path = moduleRoot + "/" + expected[i].path
|
||||||
|
}
|
||||||
|
|
||||||
|
require.ElementsMatchf(t, modules, expected, "returned modules should match")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsBuiltinModule(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
module string
|
||||||
|
expected bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Built In Should Return True",
|
||||||
|
module: "foo_bi",
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Not Built In Should Return False",
|
||||||
|
module: "not_built_in",
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
|
defer resetGlobals()
|
||||||
|
_, _ = createFiles(t)
|
||||||
|
|
||||||
|
isBuiltIn, err := isBuiltinModule(testCase.module)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, testCase.expected, isBuiltIn)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestModuleStatus(t *testing.T) {
|
||||||
|
random, err := getRandomLoadedModule(t)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("should be able to get random module")
|
||||||
|
}
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
module string
|
||||||
|
shouldBeLoaded bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Should Return Module Loading Or Greater Status",
|
||||||
|
module: random,
|
||||||
|
shouldBeLoaded: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Should Return Module Unloaded Or Lower Status",
|
||||||
|
module: "not_loaded_module",
|
||||||
|
shouldBeLoaded: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
|
defer resetGlobals()
|
||||||
|
_, _ = createFiles(t)
|
||||||
|
|
||||||
|
state, err := moduleStatus(testCase.module)
|
||||||
|
require.NoError(t, err)
|
||||||
|
if testCase.shouldBeLoaded {
|
||||||
|
require.GreaterOrEqual(t, loading, state, "moduleStatus for %s should return state loading", testCase.module)
|
||||||
|
} else {
|
||||||
|
require.Less(t, state, loading, "module should return state unloading or lower")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func resetGlobals() {
|
||||||
|
moduleLibDir = defaultModuleDir
|
||||||
|
moduleRoot = getModuleRoot()
|
||||||
|
}
|
||||||
|
|
||||||
|
func createFiles(t *testing.T) (string, []module) {
|
||||||
|
writeFile := func(path, text string) {
|
||||||
|
if err := ioutil.WriteFile(path, []byte(text), 0644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var u unix.Utsname
|
||||||
|
if err := unix.Uname(&u); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleLibDir = t.TempDir()
|
||||||
|
|
||||||
|
moduleRoot = getModuleRoot()
|
||||||
|
if err := os.Mkdir(moduleRoot, 0755); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
text := "kernel/a/foo.ko:\n"
|
||||||
|
text += "kernel/a/bar.ko: kernel/a/foo.ko\n"
|
||||||
|
text += "kernel/a/baz.ko: kernel/a/bar.ko kernel/a/foo.ko\n"
|
||||||
|
writeFile(filepath.Join(moduleRoot, "/modules.dep"), text)
|
||||||
|
|
||||||
|
text = "kernel/a/foo_bi.ko\n"
|
||||||
|
text += "kernel/a/bar-bi.ko.gz\n"
|
||||||
|
writeFile(filepath.Join(moduleRoot, "/modules.builtin"), text)
|
||||||
|
|
||||||
|
modules := []module{
|
||||||
|
{name: "foo", path: "kernel/a/foo.ko"},
|
||||||
|
{name: "bar", path: "kernel/a/bar.ko"},
|
||||||
|
{name: "baz", path: "kernel/a/baz.ko"},
|
||||||
|
}
|
||||||
|
return moduleLibDir, modules
|
||||||
|
}
|
||||||
|
|
||||||
|
func getRandomLoadedModule(t *testing.T) (string, error) {
|
||||||
|
f, err := os.Open("/proc/modules")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
err := f.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Logf("failed closing /proc/modules file, %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
lines, err := lineCounter(f)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
counter := 1
|
||||||
|
midLine := lines / 2
|
||||||
|
modName := ""
|
||||||
|
scanner := bufio.NewScanner(f)
|
||||||
|
for scanner.Scan() {
|
||||||
|
fields := strings.Fields(scanner.Text())
|
||||||
|
if counter == midLine {
|
||||||
|
if fields[4] == "Unloading" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
modName = fields[0]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
counter++
|
||||||
|
}
|
||||||
|
if scanner.Err() != nil {
|
||||||
|
return "", scanner.Err()
|
||||||
|
}
|
||||||
|
return modName, nil
|
||||||
|
}
|
||||||
|
func lineCounter(r io.Reader) (int, error) {
|
||||||
|
buf := make([]byte, 32*1024)
|
||||||
|
count := 0
|
||||||
|
lineSep := []byte{'\n'}
|
||||||
|
|
||||||
|
for {
|
||||||
|
c, err := r.Read(buf)
|
||||||
|
count += bytes.Count(buf[:c], lineSep)
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case err == io.EOF:
|
||||||
|
return count, nil
|
||||||
|
|
||||||
|
case err != nil:
|
||||||
|
return count, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,8 @@ NETBIRD_MGMT_API_ENDPOINT=https://$NETBIRD_DOMAIN:$NETBIRD_MGMT_API_PORT
|
|||||||
NETBIRD_MGMT_API_CERT_FILE="/etc/letsencrypt/live/$NETBIRD_DOMAIN/fullchain.pem"
|
NETBIRD_MGMT_API_CERT_FILE="/etc/letsencrypt/live/$NETBIRD_DOMAIN/fullchain.pem"
|
||||||
# Management Certficate key file path.
|
# Management Certficate key file path.
|
||||||
NETBIRD_MGMT_API_CERT_KEY_FILE="/etc/letsencrypt/live/$NETBIRD_DOMAIN/privkey.pem"
|
NETBIRD_MGMT_API_CERT_KEY_FILE="/etc/letsencrypt/live/$NETBIRD_DOMAIN/privkey.pem"
|
||||||
|
# By default Management single account mode is enabled and domain set to $NETBIRD_DOMAIN, you may want to set this to your user's email domain
|
||||||
|
NETBIRD_MGMT_SINGLE_ACCOUNT_MODE_DOMAIN=$NETBIRD_DOMAIN
|
||||||
|
|
||||||
# Turn credentials
|
# Turn credentials
|
||||||
|
|
||||||
@@ -29,6 +31,8 @@ LETSENCRYPT_VOLUMESUFFIX="letsencrypt"
|
|||||||
|
|
||||||
NETBIRD_AUTH_DEVICE_AUTH_PROVIDER="none"
|
NETBIRD_AUTH_DEVICE_AUTH_PROVIDER="none"
|
||||||
|
|
||||||
|
NETBIRD_DISABLE_ANONYMOUS_METRICS=${NETBIRD_DISABLE_ANONYMOUS_METRICS:-false}
|
||||||
|
|
||||||
# exports
|
# exports
|
||||||
export NETBIRD_DOMAIN
|
export NETBIRD_DOMAIN
|
||||||
export NETBIRD_AUTH_CLIENT_ID
|
export NETBIRD_AUTH_CLIENT_ID
|
||||||
@@ -45,6 +49,8 @@ export NETBIRD_MGMT_API_CERT_KEY_FILE
|
|||||||
export NETBIRD_AUTH_DEVICE_AUTH_PROVIDER
|
export NETBIRD_AUTH_DEVICE_AUTH_PROVIDER
|
||||||
export NETBIRD_AUTH_DEVICE_AUTH_CLIENT_ID
|
export NETBIRD_AUTH_DEVICE_AUTH_CLIENT_ID
|
||||||
export NETBIRD_AUTH_OIDC_CONFIGURATION_ENDPOINT
|
export NETBIRD_AUTH_OIDC_CONFIGURATION_ENDPOINT
|
||||||
|
export NETBIRD_AUTH_REDIRECT_URI
|
||||||
|
export NETBIRD_AUTH_SILENT_REDIRECT_URI
|
||||||
export TURN_USER
|
export TURN_USER
|
||||||
export TURN_PASSWORD
|
export TURN_PASSWORD
|
||||||
export TURN_MIN_PORT
|
export TURN_MIN_PORT
|
||||||
@@ -53,3 +59,4 @@ export VOLUME_PREFIX
|
|||||||
export MGMT_VOLUMESUFFIX
|
export MGMT_VOLUMESUFFIX
|
||||||
export SIGNAL_VOLUMESUFFIX
|
export SIGNAL_VOLUMESUFFIX
|
||||||
export LETSENCRYPT_VOLUMESUFFIX
|
export LETSENCRYPT_VOLUMESUFFIX
|
||||||
|
export NETBIRD_DISABLE_ANONYMOUS_METRICS
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ services:
|
|||||||
- NGINX_SSL_PORT=443
|
- NGINX_SSL_PORT=443
|
||||||
- LETSENCRYPT_DOMAIN=$NETBIRD_DOMAIN
|
- LETSENCRYPT_DOMAIN=$NETBIRD_DOMAIN
|
||||||
- LETSENCRYPT_EMAIL=$NETBIRD_LETSENCRYPT_EMAIL
|
- LETSENCRYPT_EMAIL=$NETBIRD_LETSENCRYPT_EMAIL
|
||||||
|
- AUTH_REDIRECT_URI=$NETBIRD_AUTH_REDIRECT_URI
|
||||||
|
- AUTH_SILENT_REDIRECT_URI=$NETBIRD_AUTH_SILENT_REDIRECT_URI
|
||||||
volumes:
|
volumes:
|
||||||
- $LETSENCRYPT_VOLUMENAME:/etc/letsencrypt/
|
- $LETSENCRYPT_VOLUMENAME:/etc/letsencrypt/
|
||||||
# Signal
|
# Signal
|
||||||
@@ -46,7 +48,7 @@ services:
|
|||||||
# # port and command for Let's Encrypt validation without dashboard container
|
# # port and command for Let's Encrypt validation without dashboard container
|
||||||
# - 443:443
|
# - 443:443
|
||||||
# command: ["--letsencrypt-domain", "$NETBIRD_DOMAIN", "--log-file", "console"]
|
# command: ["--letsencrypt-domain", "$NETBIRD_DOMAIN", "--log-file", "console"]
|
||||||
command: ["--port", "443", "--log-file", "console"]
|
command: ["--port", "443", "--log-file", "console", "--disable-anonymous-metrics=$NETBIRD_DISABLE_ANONYMOUS_METRICS", "--single-account-mode-domain=$NETBIRD_MGMT_SINGLE_ACCOUNT_MODE_DOMAIN"]
|
||||||
# Coturn
|
# Coturn
|
||||||
coturn:
|
coturn:
|
||||||
image: coturn/coturn
|
image: coturn/coturn
|
||||||
|
|||||||
@@ -12,4 +12,11 @@ NETBIRD_USE_AUTH0="false"
|
|||||||
NETBIRD_AUTH_DEVICE_AUTH_PROVIDER="none"
|
NETBIRD_AUTH_DEVICE_AUTH_PROVIDER="none"
|
||||||
NETBIRD_AUTH_DEVICE_AUTH_CLIENT_ID=""
|
NETBIRD_AUTH_DEVICE_AUTH_CLIENT_ID=""
|
||||||
# e.g. hello@mydomain.com
|
# e.g. hello@mydomain.com
|
||||||
NETBIRD_LETSENCRYPT_EMAIL=""
|
NETBIRD_LETSENCRYPT_EMAIL=""
|
||||||
|
# if your IDP provider doesn't support fragmented URIs, configure custom
|
||||||
|
# redirect and silent redirect URIs, these will be concatenated into your NETBIRD_DOMAIN domain.
|
||||||
|
# NETBIRD_AUTH_REDIRECT_URI="/peers"
|
||||||
|
# NETBIRD_AUTH_SILENT_REDIRECT_URI="/add-peers"
|
||||||
|
|
||||||
|
# Disable anonymous metrics collection, see more information at https://netbird.io/docs/FAQ/metrics-collection
|
||||||
|
NETBIRD_DISABLE_ANONYMOUS_METRICS=false
|
||||||
@@ -10,4 +10,5 @@ NETBIRD_AUTH_CLIENT_ID=$CI_NETBIRD_AUTH_CLIENT_ID
|
|||||||
NETBIRD_USE_AUTH0=$CI_NETBIRD_USE_AUTH0
|
NETBIRD_USE_AUTH0=$CI_NETBIRD_USE_AUTH0
|
||||||
NETBIRD_AUTH_AUDIENCE=$CI_NETBIRD_AUTH_AUDIENCE
|
NETBIRD_AUTH_AUDIENCE=$CI_NETBIRD_AUTH_AUDIENCE
|
||||||
# e.g. hello@mydomain.com
|
# e.g. hello@mydomain.com
|
||||||
NETBIRD_LETSENCRYPT_EMAIL=""
|
NETBIRD_LETSENCRYPT_EMAIL=""
|
||||||
|
NETBIRD_AUTH_REDIRECT_URI="/peers"
|
||||||
@@ -49,18 +49,18 @@ func startManagement(t *testing.T) (*grpc.Server, net.Listener) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
s := grpc.NewServer()
|
s := grpc.NewServer()
|
||||||
store, err := mgmt.NewStore(config.Datadir)
|
store, err := mgmt.NewFileStore(config.Datadir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
peersUpdateManager := mgmt.NewPeersUpdateManager()
|
peersUpdateManager := mgmt.NewPeersUpdateManager()
|
||||||
accountManager, err := mgmt.BuildManager(store, peersUpdateManager, nil)
|
accountManager, err := mgmt.BuildManager(store, peersUpdateManager, nil, "", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
turnManager := mgmt.NewTimeBasedAuthSecretsManager(peersUpdateManager, config.TURNConfig)
|
turnManager := mgmt.NewTimeBasedAuthSecretsManager(peersUpdateManager, config.TURNConfig)
|
||||||
mgmtServer, err := mgmt.NewServer(config, accountManager, peersUpdateManager, turnManager)
|
mgmtServer, err := mgmt.NewServer(config, accountManager, peersUpdateManager, turnManager, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,7 +109,9 @@ func (c *GrpcClient) Sync(msgHandler func(msg *proto.SyncResponse) error) error
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
stream, err := c.connectToStream(*serverPubKey)
|
ctx, cancelStream := context.WithCancel(c.ctx)
|
||||||
|
defer cancelStream()
|
||||||
|
stream, err := c.connectToStream(ctx, *serverPubKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debugf("failed to open Management Service stream: %s", err)
|
log.Debugf("failed to open Management Service stream: %s", err)
|
||||||
if s, ok := gstatus.FromError(err); ok && s.Code() == codes.PermissionDenied {
|
if s, ok := gstatus.FromError(err); ok && s.Code() == codes.PermissionDenied {
|
||||||
@@ -145,7 +147,7 @@ func (c *GrpcClient) Sync(msgHandler func(msg *proto.SyncResponse) error) error
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *GrpcClient) connectToStream(serverPubKey wgtypes.Key) (proto.ManagementService_SyncClient, error) {
|
func (c *GrpcClient) connectToStream(ctx context.Context, serverPubKey wgtypes.Key) (proto.ManagementService_SyncClient, error) {
|
||||||
req := &proto.SyncRequest{}
|
req := &proto.SyncRequest{}
|
||||||
|
|
||||||
myPrivateKey := c.key
|
myPrivateKey := c.key
|
||||||
@@ -156,9 +158,12 @@ func (c *GrpcClient) connectToStream(serverPubKey wgtypes.Key) (proto.Management
|
|||||||
log.Errorf("failed encrypting message: %s", err)
|
log.Errorf("failed encrypting message: %s", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
syncReq := &proto.EncryptedMessage{WgPubKey: myPublicKey.String(), Body: encryptedReq}
|
syncReq := &proto.EncryptedMessage{WgPubKey: myPublicKey.String(), Body: encryptedReq}
|
||||||
return c.realClient.Sync(c.ctx, syncReq)
|
sync, err := c.realClient.Sync(ctx, syncReq)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return sync, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *GrpcClient) receiveEvents(stream proto.ManagementService_SyncClient, serverPubKey wgtypes.Key, msgHandler func(msg *proto.SyncResponse) error) error {
|
func (c *GrpcClient) receiveEvents(stream proto.ManagementService_SyncClient, serverPubKey wgtypes.Key, msgHandler func(msg *proto.SyncResponse) error) error {
|
||||||
|
|||||||
18
management/cmd/defaults.go
Normal file
18
management/cmd/defaults.go
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultMgmtDataDir = "/var/lib/netbird/"
|
||||||
|
defaultMgmtConfigDir = "/etc/netbird"
|
||||||
|
defaultLogDir = "/var/log/netbird"
|
||||||
|
|
||||||
|
oldDefaultMgmtDataDir = "/var/lib/wiretrustee/"
|
||||||
|
oldDefaultMgmtConfigDir = "/etc/wiretrustee"
|
||||||
|
oldDefaultLogDir = "/var/log/wiretrustee"
|
||||||
|
|
||||||
|
defaultMgmtConfig = defaultMgmtConfigDir + "/management.json"
|
||||||
|
defaultLogFile = defaultLogDir + "/management.log"
|
||||||
|
oldDefaultMgmtConfig = oldDefaultMgmtConfigDir + "/management.json"
|
||||||
|
oldDefaultLogFile = oldDefaultLogDir + "/management.log"
|
||||||
|
|
||||||
|
defaultSingleAccModeDomain = "netbird.selfhosted"
|
||||||
|
)
|
||||||
@@ -1,12 +1,17 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/miekg/dns"
|
||||||
httpapi "github.com/netbirdio/netbird/management/server/http"
|
httpapi "github.com/netbirdio/netbird/management/server/http"
|
||||||
|
"github.com/netbirdio/netbird/management/server/metrics"
|
||||||
|
"github.com/netbirdio/netbird/management/server/telemetry"
|
||||||
"golang.org/x/crypto/acme/autocert"
|
"golang.org/x/crypto/acme/autocert"
|
||||||
"golang.org/x/net/http2"
|
"golang.org/x/net/http2"
|
||||||
"golang.org/x/net/http2/h2c"
|
"golang.org/x/net/http2/h2c"
|
||||||
@@ -38,11 +43,13 @@ import (
|
|||||||
const ManagementLegacyPort = 33073
|
const ManagementLegacyPort = 33073
|
||||||
|
|
||||||
var (
|
var (
|
||||||
mgmtPort int
|
mgmtPort int
|
||||||
mgmtLetsencryptDomain string
|
mgmtMetricsPort int
|
||||||
certFile string
|
mgmtLetsencryptDomain string
|
||||||
certKey string
|
mgmtSingleAccModeDomain string
|
||||||
config *server.Config
|
certFile string
|
||||||
|
certKey string
|
||||||
|
config *server.Config
|
||||||
|
|
||||||
kaep = keepalive.EnforcementPolicy{
|
kaep = keepalive.EnforcementPolicy{
|
||||||
MinTime: 15 * time.Second,
|
MinTime: 15 * time.Second,
|
||||||
@@ -83,6 +90,11 @@ var (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_, valid := dns.IsDomainName(dnsDomain)
|
||||||
|
if !valid || len(dnsDomain) > 192 {
|
||||||
|
return fmt.Errorf("failed parsing the provided dns-domain. Valid status: %t, Lenght: %d", valid, len(dnsDomain))
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
@@ -104,21 +116,33 @@ var (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
store, err := server.NewStore(config.Datadir)
|
store, err := server.NewFileStore(config.Datadir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed creating Store: %s: %v", config.Datadir, err)
|
return fmt.Errorf("failed creating Store: %s: %v", config.Datadir, err)
|
||||||
}
|
}
|
||||||
peersUpdateManager := server.NewPeersUpdateManager()
|
peersUpdateManager := server.NewPeersUpdateManager()
|
||||||
|
|
||||||
|
appMetrics, err := telemetry.NewDefaultAppMetrics(cmd.Context())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = appMetrics.Expose(mgmtMetricsPort, "/metrics")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
var idpManager idp.Manager
|
var idpManager idp.Manager
|
||||||
if config.IdpManagerConfig != nil {
|
if config.IdpManagerConfig != nil {
|
||||||
idpManager, err = idp.NewManager(*config.IdpManagerConfig)
|
idpManager, err = idp.NewManager(*config.IdpManagerConfig, appMetrics)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed retrieving a new idp manager with err: %v", err)
|
return fmt.Errorf("failed retrieving a new idp manager with err: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
accountManager, err := server.BuildManager(store, peersUpdateManager, idpManager)
|
if disableSingleAccMode {
|
||||||
|
mgmtSingleAccModeDomain = ""
|
||||||
|
}
|
||||||
|
accountManager, err := server.BuildManager(store, peersUpdateManager, idpManager, mgmtSingleAccModeDomain, dnsDomain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to build default manager: %v", err)
|
return fmt.Errorf("failed to build default manager: %v", err)
|
||||||
}
|
}
|
||||||
@@ -148,19 +172,32 @@ var (
|
|||||||
tlsEnabled = true
|
tlsEnabled = true
|
||||||
}
|
}
|
||||||
|
|
||||||
httpAPIHandler, err := httpapi.APIHandler(accountManager,
|
httpAPIHandler, err := httpapi.APIHandler(accountManager, config.HttpConfig.AuthIssuer,
|
||||||
config.HttpConfig.AuthIssuer, config.HttpConfig.AuthAudience, config.HttpConfig.AuthKeysLocation)
|
config.HttpConfig.AuthAudience, config.HttpConfig.AuthKeysLocation, appMetrics)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed creating HTTP API handler: %v", err)
|
return fmt.Errorf("failed creating HTTP API handler: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
gRPCAPIHandler := grpc.NewServer(gRPCOpts...)
|
gRPCAPIHandler := grpc.NewServer(gRPCOpts...)
|
||||||
srv, err := server.NewServer(config, accountManager, peersUpdateManager, turnManager)
|
srv, err := server.NewServer(config, accountManager, peersUpdateManager, turnManager, appMetrics)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed creating gRPC API handler: %v", err)
|
return fmt.Errorf("failed creating gRPC API handler: %v", err)
|
||||||
}
|
}
|
||||||
mgmtProto.RegisterManagementServiceServer(gRPCAPIHandler, srv)
|
mgmtProto.RegisterManagementServiceServer(gRPCAPIHandler, srv)
|
||||||
|
|
||||||
|
installationID, err := getInstallationID(store)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("cannot load TLS credentials: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !disableMetrics {
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
metricsWorker := metrics.NewWorker(ctx, installationID, store, peersUpdateManager)
|
||||||
|
go metricsWorker.Run()
|
||||||
|
}
|
||||||
|
|
||||||
var compatListener net.Listener
|
var compatListener net.Listener
|
||||||
if mgmtPort != ManagementLegacyPort {
|
if mgmtPort != ManagementLegacyPort {
|
||||||
// The Management gRPC server was running on port 33073 previously. Old agents that are already connected to it
|
// The Management gRPC server was running on port 33073 previously. Old agents that are already connected to it
|
||||||
@@ -207,11 +244,13 @@ var (
|
|||||||
SetupCloseHandler()
|
SetupCloseHandler()
|
||||||
|
|
||||||
<-stopCh
|
<-stopCh
|
||||||
|
_ = appMetrics.Close()
|
||||||
_ = listener.Close()
|
_ = listener.Close()
|
||||||
if certManager != nil {
|
if certManager != nil {
|
||||||
_ = certManager.Listener().Close()
|
_ = certManager.Listener().Close()
|
||||||
}
|
}
|
||||||
gRPCAPIHandler.Stop()
|
gRPCAPIHandler.Stop()
|
||||||
|
_ = store.Close()
|
||||||
log.Infof("stopped Management Service")
|
log.Infof("stopped Management Service")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -228,6 +267,20 @@ func notifyStop(msg string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getInstallationID(store server.Store) (string, error) {
|
||||||
|
installationID := store.GetInstallationID()
|
||||||
|
if installationID != "" {
|
||||||
|
return installationID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
installationID = strings.ToUpper(uuid.New().String())
|
||||||
|
err := store.SaveInstallationID(installationID)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return installationID, nil
|
||||||
|
}
|
||||||
|
|
||||||
func serveGRPC(grpcServer *grpc.Server, port int) (net.Listener, error) {
|
func serveGRPC(grpcServer *grpc.Server, port int) (net.Listener, error) {
|
||||||
listener, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
|
listener, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -395,7 +448,7 @@ func loadTLSConfig(certFile string, certKey string) (*tls.Config, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the credentials and return it
|
// NewDefaultAppMetrics the credentials and return it
|
||||||
config := &tls.Config{
|
config := &tls.Config{
|
||||||
Certificates: []tls.Certificate{serverCert},
|
Certificates: []tls.Certificate{serverCert},
|
||||||
ClientAuth: tls.NoClientCert,
|
ClientAuth: tls.NoClientCert,
|
||||||
|
|||||||
@@ -13,20 +13,13 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
defaultMgmtConfigDir string
|
dnsDomain string
|
||||||
defaultMgmtDataDir string
|
mgmtDataDir string
|
||||||
defaultMgmtConfig string
|
mgmtConfig string
|
||||||
defaultLogDir string
|
logLevel string
|
||||||
defaultLogFile string
|
logFile string
|
||||||
oldDefaultMgmtConfigDir string
|
disableMetrics bool
|
||||||
oldDefaultMgmtDataDir string
|
disableSingleAccMode bool
|
||||||
oldDefaultMgmtConfig string
|
|
||||||
oldDefaultLogDir string
|
|
||||||
oldDefaultLogFile string
|
|
||||||
mgmtDataDir string
|
|
||||||
mgmtConfig string
|
|
||||||
logLevel string
|
|
||||||
logFile string
|
|
||||||
|
|
||||||
rootCmd = &cobra.Command{
|
rootCmd = &cobra.Command{
|
||||||
Use: "netbird-mgmt",
|
Use: "netbird-mgmt",
|
||||||
@@ -45,27 +38,17 @@ func Execute() error {
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
stopCh = make(chan int)
|
stopCh = make(chan int)
|
||||||
|
|
||||||
defaultMgmtDataDir = "/var/lib/netbird/"
|
|
||||||
defaultMgmtConfigDir = "/etc/netbird"
|
|
||||||
defaultLogDir = "/var/log/netbird"
|
|
||||||
|
|
||||||
oldDefaultMgmtDataDir = "/var/lib/wiretrustee/"
|
|
||||||
oldDefaultMgmtConfigDir = "/etc/wiretrustee"
|
|
||||||
oldDefaultLogDir = "/var/log/wiretrustee"
|
|
||||||
|
|
||||||
defaultMgmtConfig = defaultMgmtConfigDir + "/management.json"
|
|
||||||
defaultLogFile = defaultLogDir + "/management.log"
|
|
||||||
|
|
||||||
oldDefaultMgmtConfig = oldDefaultMgmtConfigDir + "/management.json"
|
|
||||||
oldDefaultLogFile = oldDefaultLogDir + "/management.log"
|
|
||||||
|
|
||||||
mgmtCmd.Flags().IntVar(&mgmtPort, "port", 80, "server port to listen on (defaults to 443 if TLS is enabled, 80 otherwise")
|
mgmtCmd.Flags().IntVar(&mgmtPort, "port", 80, "server port to listen on (defaults to 443 if TLS is enabled, 80 otherwise")
|
||||||
|
mgmtCmd.Flags().IntVar(&mgmtMetricsPort, "metrics-port", 8081, "metrics endpoint http port. Metrics are accessible under host:metrics-port/metrics")
|
||||||
mgmtCmd.Flags().StringVar(&mgmtDataDir, "datadir", defaultMgmtDataDir, "server data directory location")
|
mgmtCmd.Flags().StringVar(&mgmtDataDir, "datadir", defaultMgmtDataDir, "server data directory location")
|
||||||
mgmtCmd.Flags().StringVar(&mgmtConfig, "config", defaultMgmtConfig, "Netbird 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", defaultMgmtConfig, "Netbird 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(&mgmtSingleAccModeDomain, "single-account-mode-domain", defaultSingleAccModeDomain, "Enables single account mode. This means that all the users will be under the same account grouped by the specified domain. If the installation has more than one account, the property is ineffective. Enabled by default with the default domain "+defaultSingleAccModeDomain)
|
||||||
|
mgmtCmd.Flags().BoolVar(&disableSingleAccMode, "disable-single-account-mode", false, "If set to true, disables single account mode. The --single-account-mode-domain property will be ignored and every new user will have a separate NetBird account.")
|
||||||
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(&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")
|
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")
|
||||||
|
mgmtCmd.Flags().BoolVar(&disableMetrics, "disable-anonymous-metrics", false, "disables push of anonymous usage metrics to NetBird")
|
||||||
|
mgmtCmd.Flags().StringVar(&dnsDomain, "dns-domain", defaultSingleAccModeDomain, fmt.Sprintf("Domain used for peer resolution. This is appended to the peer's name, e.g. pi-server. %s. Max lenght is 192 characters to allow appending to a peer name with up to 63 characters.", defaultSingleAccModeDomain))
|
||||||
rootCmd.MarkFlagRequired("config") //nolint
|
rootCmd.MarkFlagRequired("config") //nolint
|
||||||
|
|
||||||
rootCmd.PersistentFlags().StringVar(&logLevel, "log-level", "info", "")
|
rootCmd.PersistentFlags().StringVar(&logLevel, "log-level", "info", "")
|
||||||
|
|||||||
@@ -1,4 +1,17 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
if ! which realpath > /dev/null 2>&1
|
||||||
|
then
|
||||||
|
echo realpath is not installed
|
||||||
|
echo run: brew install coreutils
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
old_pwd=$(pwd)
|
||||||
|
script_path=$(dirname $(realpath "$0"))
|
||||||
|
cd "$script_path"
|
||||||
go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.26
|
go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.26
|
||||||
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.1
|
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.1
|
||||||
protoc -I proto/ proto/management.proto --go_out=. --go-grpc_out=.
|
protoc -I ./ ./management.proto --go_out=../ --go-grpc_out=../
|
||||||
|
cd "$old_pwd"
|
||||||
@@ -1,15 +1,15 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.26.0
|
// protoc-gen-go v1.26.0
|
||||||
// protoc v3.12.4
|
// protoc v3.21.9
|
||||||
// source: management.proto
|
// source: management.proto
|
||||||
|
|
||||||
package proto
|
package proto
|
||||||
|
|
||||||
import (
|
import (
|
||||||
timestamp "github.com/golang/protobuf/ptypes/timestamp"
|
|
||||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
|
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
|
||||||
reflect "reflect"
|
reflect "reflect"
|
||||||
sync "sync"
|
sync "sync"
|
||||||
)
|
)
|
||||||
@@ -611,7 +611,7 @@ type ServerKeyResponse struct {
|
|||||||
// Server's Wireguard public key
|
// Server's Wireguard public key
|
||||||
Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
|
Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
|
||||||
// Key expiration timestamp after which the key should be fetched again by the client
|
// Key expiration timestamp after which the key should be fetched again by the client
|
||||||
ExpiresAt *timestamp.Timestamp `protobuf:"bytes,2,opt,name=expiresAt,proto3" json:"expiresAt,omitempty"`
|
ExpiresAt *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=expiresAt,proto3" json:"expiresAt,omitempty"`
|
||||||
// Version of the Wiretrustee Management Service protocol
|
// Version of the Wiretrustee Management Service protocol
|
||||||
Version int32 `protobuf:"varint,3,opt,name=version,proto3" json:"version,omitempty"`
|
Version int32 `protobuf:"varint,3,opt,name=version,proto3" json:"version,omitempty"`
|
||||||
}
|
}
|
||||||
@@ -655,7 +655,7 @@ func (x *ServerKeyResponse) GetKey() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *ServerKeyResponse) GetExpiresAt() *timestamp.Timestamp {
|
func (x *ServerKeyResponse) GetExpiresAt() *timestamppb.Timestamp {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.ExpiresAt
|
return x.ExpiresAt
|
||||||
}
|
}
|
||||||
@@ -982,6 +982,8 @@ type NetworkMap struct {
|
|||||||
RemotePeersIsEmpty bool `protobuf:"varint,4,opt,name=remotePeersIsEmpty,proto3" json:"remotePeersIsEmpty,omitempty"`
|
RemotePeersIsEmpty bool `protobuf:"varint,4,opt,name=remotePeersIsEmpty,proto3" json:"remotePeersIsEmpty,omitempty"`
|
||||||
// List of routes to be applied
|
// List of routes to be applied
|
||||||
Routes []*Route `protobuf:"bytes,5,rep,name=Routes,proto3" json:"Routes,omitempty"`
|
Routes []*Route `protobuf:"bytes,5,rep,name=Routes,proto3" json:"Routes,omitempty"`
|
||||||
|
// DNS config to be applied
|
||||||
|
DNSConfig *DNSConfig `protobuf:"bytes,6,opt,name=DNSConfig,proto3" json:"DNSConfig,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *NetworkMap) Reset() {
|
func (x *NetworkMap) Reset() {
|
||||||
@@ -1051,6 +1053,13 @@ func (x *NetworkMap) GetRoutes() []*Route {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *NetworkMap) GetDNSConfig() *DNSConfig {
|
||||||
|
if x != nil {
|
||||||
|
return x.DNSConfig
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// RemotePeerConfig represents a configuration of a remote peer.
|
// RemotePeerConfig represents a configuration of a remote peer.
|
||||||
// The properties are used to configure Wireguard Peers sections
|
// The properties are used to configure Wireguard Peers sections
|
||||||
type RemotePeerConfig struct {
|
type RemotePeerConfig struct {
|
||||||
@@ -1467,6 +1476,334 @@ func (x *Route) GetNetID() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DNSConfig represents a dns.Update
|
||||||
|
type DNSConfig struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
ServiceEnable bool `protobuf:"varint,1,opt,name=ServiceEnable,proto3" json:"ServiceEnable,omitempty"`
|
||||||
|
NameServerGroups []*NameServerGroup `protobuf:"bytes,2,rep,name=NameServerGroups,proto3" json:"NameServerGroups,omitempty"`
|
||||||
|
CustomZones []*CustomZone `protobuf:"bytes,3,rep,name=CustomZones,proto3" json:"CustomZones,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DNSConfig) Reset() {
|
||||||
|
*x = DNSConfig{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_management_proto_msgTypes[20]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DNSConfig) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*DNSConfig) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *DNSConfig) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_management_proto_msgTypes[20]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use DNSConfig.ProtoReflect.Descriptor instead.
|
||||||
|
func (*DNSConfig) Descriptor() ([]byte, []int) {
|
||||||
|
return file_management_proto_rawDescGZIP(), []int{20}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DNSConfig) GetServiceEnable() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.ServiceEnable
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DNSConfig) GetNameServerGroups() []*NameServerGroup {
|
||||||
|
if x != nil {
|
||||||
|
return x.NameServerGroups
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DNSConfig) GetCustomZones() []*CustomZone {
|
||||||
|
if x != nil {
|
||||||
|
return x.CustomZones
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CustomZone represents a dns.CustomZone
|
||||||
|
type CustomZone struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
Domain string `protobuf:"bytes,1,opt,name=Domain,proto3" json:"Domain,omitempty"`
|
||||||
|
Records []*SimpleRecord `protobuf:"bytes,2,rep,name=Records,proto3" json:"Records,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *CustomZone) Reset() {
|
||||||
|
*x = CustomZone{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_management_proto_msgTypes[21]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *CustomZone) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*CustomZone) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *CustomZone) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_management_proto_msgTypes[21]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use CustomZone.ProtoReflect.Descriptor instead.
|
||||||
|
func (*CustomZone) Descriptor() ([]byte, []int) {
|
||||||
|
return file_management_proto_rawDescGZIP(), []int{21}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *CustomZone) GetDomain() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Domain
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *CustomZone) GetRecords() []*SimpleRecord {
|
||||||
|
if x != nil {
|
||||||
|
return x.Records
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SimpleRecord represents a dns.SimpleRecord
|
||||||
|
type SimpleRecord struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
Name string `protobuf:"bytes,1,opt,name=Name,proto3" json:"Name,omitempty"`
|
||||||
|
Type int64 `protobuf:"varint,2,opt,name=Type,proto3" json:"Type,omitempty"`
|
||||||
|
Class string `protobuf:"bytes,3,opt,name=Class,proto3" json:"Class,omitempty"`
|
||||||
|
TTL int64 `protobuf:"varint,4,opt,name=TTL,proto3" json:"TTL,omitempty"`
|
||||||
|
RData string `protobuf:"bytes,5,opt,name=RData,proto3" json:"RData,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *SimpleRecord) Reset() {
|
||||||
|
*x = SimpleRecord{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_management_proto_msgTypes[22]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *SimpleRecord) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*SimpleRecord) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *SimpleRecord) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_management_proto_msgTypes[22]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use SimpleRecord.ProtoReflect.Descriptor instead.
|
||||||
|
func (*SimpleRecord) Descriptor() ([]byte, []int) {
|
||||||
|
return file_management_proto_rawDescGZIP(), []int{22}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *SimpleRecord) GetName() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Name
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *SimpleRecord) GetType() int64 {
|
||||||
|
if x != nil {
|
||||||
|
return x.Type
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *SimpleRecord) GetClass() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Class
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *SimpleRecord) GetTTL() int64 {
|
||||||
|
if x != nil {
|
||||||
|
return x.TTL
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *SimpleRecord) GetRData() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.RData
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// NameServerGroup represents a dns.NameServerGroup
|
||||||
|
type NameServerGroup struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
NameServers []*NameServer `protobuf:"bytes,1,rep,name=NameServers,proto3" json:"NameServers,omitempty"`
|
||||||
|
Primary bool `protobuf:"varint,2,opt,name=Primary,proto3" json:"Primary,omitempty"`
|
||||||
|
Domains []string `protobuf:"bytes,3,rep,name=Domains,proto3" json:"Domains,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *NameServerGroup) Reset() {
|
||||||
|
*x = NameServerGroup{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_management_proto_msgTypes[23]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *NameServerGroup) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*NameServerGroup) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *NameServerGroup) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_management_proto_msgTypes[23]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use NameServerGroup.ProtoReflect.Descriptor instead.
|
||||||
|
func (*NameServerGroup) Descriptor() ([]byte, []int) {
|
||||||
|
return file_management_proto_rawDescGZIP(), []int{23}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *NameServerGroup) GetNameServers() []*NameServer {
|
||||||
|
if x != nil {
|
||||||
|
return x.NameServers
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *NameServerGroup) GetPrimary() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.Primary
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *NameServerGroup) GetDomains() []string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Domains
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NameServer represents a dns.NameServer
|
||||||
|
type NameServer struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
IP string `protobuf:"bytes,1,opt,name=IP,proto3" json:"IP,omitempty"`
|
||||||
|
NSType int64 `protobuf:"varint,2,opt,name=NSType,proto3" json:"NSType,omitempty"`
|
||||||
|
Port int64 `protobuf:"varint,3,opt,name=Port,proto3" json:"Port,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *NameServer) Reset() {
|
||||||
|
*x = NameServer{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_management_proto_msgTypes[24]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *NameServer) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*NameServer) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *NameServer) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_management_proto_msgTypes[24]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use NameServer.ProtoReflect.Descriptor instead.
|
||||||
|
func (*NameServer) Descriptor() ([]byte, []int) {
|
||||||
|
return file_management_proto_rawDescGZIP(), []int{24}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *NameServer) GetIP() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.IP
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *NameServer) GetNSType() int64 {
|
||||||
|
if x != nil {
|
||||||
|
return x.NSType
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *NameServer) GetPort() int64 {
|
||||||
|
if x != nil {
|
||||||
|
return x.Port
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
var File_management_proto protoreflect.FileDescriptor
|
var File_management_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
var file_management_proto_rawDesc = []byte{
|
var file_management_proto_rawDesc = []byte{
|
||||||
@@ -1583,7 +1920,7 @@ var file_management_proto_rawDesc = []byte{
|
|||||||
0x0a, 0x09, 0x73, 0x73, 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28,
|
0x0a, 0x09, 0x73, 0x73, 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28,
|
||||||
0x0b, 0x32, 0x15, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x53,
|
0x0b, 0x32, 0x15, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x53,
|
||||||
0x53, 0x48, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x09, 0x73, 0x73, 0x68, 0x43, 0x6f, 0x6e,
|
0x53, 0x48, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x09, 0x73, 0x73, 0x68, 0x43, 0x6f, 0x6e,
|
||||||
0x66, 0x69, 0x67, 0x22, 0xf7, 0x01, 0x0a, 0x0a, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x4d,
|
0x66, 0x69, 0x67, 0x22, 0xac, 0x02, 0x0a, 0x0a, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x4d,
|
||||||
0x61, 0x70, 0x12, 0x16, 0x0a, 0x06, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x18, 0x01, 0x20, 0x01,
|
0x61, 0x70, 0x12, 0x16, 0x0a, 0x06, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x18, 0x01, 0x20, 0x01,
|
||||||
0x28, 0x04, 0x52, 0x06, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x12, 0x36, 0x0a, 0x0a, 0x70, 0x65,
|
0x28, 0x04, 0x52, 0x06, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x12, 0x36, 0x0a, 0x0a, 0x70, 0x65,
|
||||||
0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16,
|
0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16,
|
||||||
@@ -1598,85 +1935,125 @@ var file_management_proto_rawDesc = []byte{
|
|||||||
0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x65, 0x72, 0x73, 0x49, 0x73, 0x45, 0x6d, 0x70,
|
0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x65, 0x72, 0x73, 0x49, 0x73, 0x45, 0x6d, 0x70,
|
||||||
0x74, 0x79, 0x12, 0x29, 0x0a, 0x06, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03,
|
0x74, 0x79, 0x12, 0x29, 0x0a, 0x06, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03,
|
||||||
0x28, 0x0b, 0x32, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e,
|
0x28, 0x0b, 0x32, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e,
|
||||||
0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x06, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x22, 0x83, 0x01,
|
0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x06, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x12, 0x33, 0x0a,
|
||||||
0x0a, 0x10, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66,
|
0x09, 0x44, 0x4e, 0x53, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b,
|
||||||
0x69, 0x67, 0x12, 0x1a, 0x0a, 0x08, 0x77, 0x67, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x18, 0x01,
|
0x32, 0x15, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x44, 0x4e,
|
||||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x77, 0x67, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x1e,
|
0x53, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x09, 0x44, 0x4e, 0x53, 0x43, 0x6f, 0x6e, 0x66,
|
||||||
0x0a, 0x0a, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x49, 0x70, 0x73, 0x18, 0x02, 0x20, 0x03,
|
0x69, 0x67, 0x22, 0x83, 0x01, 0x0a, 0x10, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x65,
|
||||||
0x28, 0x09, 0x52, 0x0a, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x49, 0x70, 0x73, 0x12, 0x33,
|
0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1a, 0x0a, 0x08, 0x77, 0x67, 0x50, 0x75, 0x62,
|
||||||
0x0a, 0x09, 0x73, 0x73, 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28,
|
0x4b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x77, 0x67, 0x50, 0x75, 0x62,
|
||||||
0x0b, 0x32, 0x15, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x53,
|
0x4b, 0x65, 0x79, 0x12, 0x1e, 0x0a, 0x0a, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x49, 0x70,
|
||||||
0x53, 0x48, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x09, 0x73, 0x73, 0x68, 0x43, 0x6f, 0x6e,
|
0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64,
|
||||||
0x66, 0x69, 0x67, 0x22, 0x49, 0x0a, 0x09, 0x53, 0x53, 0x48, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
|
0x49, 0x70, 0x73, 0x12, 0x33, 0x0a, 0x09, 0x73, 0x73, 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
|
||||||
0x12, 0x1e, 0x0a, 0x0a, 0x73, 0x73, 0x68, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01,
|
0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d,
|
||||||
0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x73, 0x73, 0x68, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64,
|
0x65, 0x6e, 0x74, 0x2e, 0x53, 0x53, 0x48, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x09, 0x73,
|
||||||
0x12, 0x1c, 0x0a, 0x09, 0x73, 0x73, 0x68, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20,
|
0x73, 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x49, 0x0a, 0x09, 0x53, 0x53, 0x48, 0x43,
|
||||||
0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x73, 0x68, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x22, 0x20,
|
0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1e, 0x0a, 0x0a, 0x73, 0x73, 0x68, 0x45, 0x6e, 0x61, 0x62,
|
||||||
0x0a, 0x1e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a,
|
0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x73, 0x73, 0x68, 0x45, 0x6e,
|
||||||
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
|
0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x73, 0x68, 0x50, 0x75, 0x62, 0x4b,
|
||||||
0x22, 0xbf, 0x01, 0x0a, 0x17, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f,
|
0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x73, 0x68, 0x50, 0x75, 0x62,
|
||||||
0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x12, 0x48, 0x0a, 0x08,
|
0x4b, 0x65, 0x79, 0x22, 0x20, 0x0a, 0x1e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x41, 0x75, 0x74,
|
||||||
0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2c,
|
0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x52, 0x65,
|
||||||
0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x44, 0x65, 0x76, 0x69,
|
0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xbf, 0x01, 0x0a, 0x17, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65,
|
||||||
0x63, 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46,
|
0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f,
|
||||||
0x6c, 0x6f, 0x77, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x52, 0x08, 0x50, 0x72,
|
0x77, 0x12, 0x48, 0x0a, 0x08, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20,
|
||||||
0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x42, 0x0a, 0x0e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64,
|
0x01, 0x28, 0x0e, 0x32, 0x2c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74,
|
||||||
0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a,
|
0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61,
|
||||||
0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x72, 0x6f, 0x76,
|
0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65,
|
||||||
0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0e, 0x50, 0x72, 0x6f, 0x76,
|
0x72, 0x52, 0x08, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x42, 0x0a, 0x0e, 0x50,
|
||||||
0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x16, 0x0a, 0x08, 0x70, 0x72,
|
0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x02, 0x20,
|
||||||
0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x0a, 0x0a, 0x06, 0x48, 0x4f, 0x53, 0x54, 0x45, 0x44,
|
0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74,
|
||||||
0x10, 0x00, 0x22, 0xda, 0x01, 0x0a, 0x0e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43,
|
0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52,
|
||||||
0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1a, 0x0a, 0x08, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49,
|
0x0e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22,
|
||||||
0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49,
|
0x16, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x0a, 0x0a, 0x06, 0x48,
|
||||||
0x44, 0x12, 0x22, 0x0a, 0x0c, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65,
|
0x4f, 0x53, 0x54, 0x45, 0x44, 0x10, 0x00, 0x22, 0xda, 0x01, 0x0a, 0x0e, 0x50, 0x72, 0x6f, 0x76,
|
||||||
0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x53,
|
0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1a, 0x0a, 0x08, 0x43, 0x6c,
|
||||||
0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18,
|
0x69, 0x65, 0x6e, 0x74, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x43, 0x6c,
|
||||||
0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x1a, 0x0a,
|
0x69, 0x65, 0x6e, 0x74, 0x49, 0x44, 0x12, 0x22, 0x0a, 0x0c, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74,
|
||||||
0x08, 0x41, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52,
|
0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x43, 0x6c,
|
||||||
0x08, 0x41, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x2e, 0x0a, 0x12, 0x44, 0x65, 0x76,
|
0x69, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x44, 0x6f,
|
||||||
0x69, 0x63, 0x65, 0x41, 0x75, 0x74, 0x68, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18,
|
0x6d, 0x61, 0x69, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x44, 0x6f, 0x6d, 0x61,
|
||||||
0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x41, 0x75, 0x74,
|
0x69, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x41, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x04,
|
||||||
0x68, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x24, 0x0a, 0x0d, 0x54, 0x6f, 0x6b,
|
0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x41, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x2e,
|
||||||
0x65, 0x6e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09,
|
0x0a, 0x12, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x41, 0x75, 0x74, 0x68, 0x45, 0x6e, 0x64, 0x70,
|
||||||
0x52, 0x0d, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x22,
|
0x6f, 0x69, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x44, 0x65, 0x76, 0x69,
|
||||||
0xb5, 0x01, 0x0a, 0x05, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x44, 0x18,
|
0x63, 0x65, 0x41, 0x75, 0x74, 0x68, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x24,
|
||||||
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x44, 0x12, 0x18, 0x0a, 0x07, 0x4e, 0x65, 0x74,
|
0x0a, 0x0d, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18,
|
||||||
0x77, 0x6f, 0x72, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x4e, 0x65, 0x74, 0x77,
|
0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x45, 0x6e, 0x64, 0x70,
|
||||||
0x6f, 0x72, 0x6b, 0x12, 0x20, 0x0a, 0x0b, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x54, 0x79,
|
0x6f, 0x69, 0x6e, 0x74, 0x22, 0xb5, 0x01, 0x0a, 0x05, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x0e,
|
||||||
0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72,
|
0x0a, 0x02, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x44, 0x12, 0x18,
|
||||||
0x6b, 0x54, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x50, 0x65, 0x65, 0x72, 0x18, 0x04, 0x20,
|
0x0a, 0x07, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||||
0x01, 0x28, 0x09, 0x52, 0x04, 0x50, 0x65, 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x4d, 0x65, 0x74,
|
0x07, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x12, 0x20, 0x0a, 0x0b, 0x4e, 0x65, 0x74, 0x77,
|
||||||
0x72, 0x69, 0x63, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x4d, 0x65, 0x74, 0x72, 0x69,
|
0x6f, 0x72, 0x6b, 0x54, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x4e,
|
||||||
0x63, 0x12, 0x1e, 0x0a, 0x0a, 0x4d, 0x61, 0x73, 0x71, 0x75, 0x65, 0x72, 0x61, 0x64, 0x65, 0x18,
|
0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x54, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x50, 0x65,
|
||||||
0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x4d, 0x61, 0x73, 0x71, 0x75, 0x65, 0x72, 0x61, 0x64,
|
0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x50, 0x65, 0x65, 0x72, 0x12, 0x16,
|
||||||
0x65, 0x12, 0x14, 0x0a, 0x05, 0x4e, 0x65, 0x74, 0x49, 0x44, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09,
|
0x0a, 0x06, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06,
|
||||||
0x52, 0x05, 0x4e, 0x65, 0x74, 0x49, 0x44, 0x32, 0xf7, 0x02, 0x0a, 0x11, 0x4d, 0x61, 0x6e, 0x61,
|
0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x12, 0x1e, 0x0a, 0x0a, 0x4d, 0x61, 0x73, 0x71, 0x75, 0x65,
|
||||||
0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x45, 0x0a,
|
0x72, 0x61, 0x64, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x4d, 0x61, 0x73, 0x71,
|
||||||
0x05, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d,
|
0x75, 0x65, 0x72, 0x61, 0x64, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x4e, 0x65, 0x74, 0x49, 0x44, 0x18,
|
||||||
0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73,
|
0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x4e, 0x65, 0x74, 0x49, 0x44, 0x22, 0xb4, 0x01, 0x0a,
|
||||||
0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e,
|
0x09, 0x44, 0x4e, 0x53, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x24, 0x0a, 0x0d, 0x53, 0x65,
|
||||||
0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61,
|
0x72, 0x76, 0x69, 0x63, 0x65, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
|
||||||
0x67, 0x65, 0x22, 0x00, 0x12, 0x46, 0x0a, 0x04, 0x53, 0x79, 0x6e, 0x63, 0x12, 0x1c, 0x2e, 0x6d,
|
0x08, 0x52, 0x0d, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65,
|
||||||
0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70,
|
0x12, 0x47, 0x0a, 0x10, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x47, 0x72,
|
||||||
0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e,
|
0x6f, 0x75, 0x70, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6d, 0x61, 0x6e,
|
||||||
0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65,
|
0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76,
|
||||||
0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x30, 0x01, 0x12, 0x42, 0x0a, 0x0c,
|
0x65, 0x72, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x10, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72,
|
||||||
0x47, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x12, 0x11, 0x2e, 0x6d,
|
0x76, 0x65, 0x72, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x12, 0x38, 0x0a, 0x0b, 0x43, 0x75, 0x73,
|
||||||
0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a,
|
0x74, 0x6f, 0x6d, 0x5a, 0x6f, 0x6e, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16,
|
||||||
0x1d, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x65, 0x72,
|
0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x43, 0x75, 0x73, 0x74,
|
||||||
0x76, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00,
|
0x6f, 0x6d, 0x5a, 0x6f, 0x6e, 0x65, 0x52, 0x0b, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5a, 0x6f,
|
||||||
0x12, 0x33, 0x0a, 0x09, 0x69, 0x73, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x79, 0x12, 0x11, 0x2e,
|
0x6e, 0x65, 0x73, 0x22, 0x58, 0x0a, 0x0a, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5a, 0x6f, 0x6e,
|
||||||
0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79,
|
0x65, 0x12, 0x16, 0x0a, 0x06, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28,
|
||||||
0x1a, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d,
|
0x09, 0x52, 0x06, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x32, 0x0a, 0x07, 0x52, 0x65, 0x63,
|
||||||
0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x5a, 0x0a, 0x1a, 0x47, 0x65, 0x74, 0x44, 0x65, 0x76, 0x69,
|
0x6f, 0x72, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6d, 0x61, 0x6e,
|
||||||
0x63, 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46,
|
0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x52, 0x65,
|
||||||
0x6c, 0x6f, 0x77, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74,
|
0x63, 0x6f, 0x72, 0x64, 0x52, 0x07, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x22, 0x74, 0x0a,
|
||||||
|
0x0c, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x12, 0x12, 0x0a,
|
||||||
|
0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d,
|
||||||
|
0x65, 0x12, 0x12, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52,
|
||||||
|
0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x18, 0x03,
|
||||||
|
0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x54,
|
||||||
|
0x54, 0x4c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x54, 0x54, 0x4c, 0x12, 0x14, 0x0a,
|
||||||
|
0x05, 0x52, 0x44, 0x61, 0x74, 0x61, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x52, 0x44,
|
||||||
|
0x61, 0x74, 0x61, 0x22, 0x7f, 0x0a, 0x0f, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65,
|
||||||
|
0x72, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x38, 0x0a, 0x0b, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65,
|
||||||
|
0x72, 0x76, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d, 0x61,
|
||||||
|
0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72,
|
||||||
|
0x76, 0x65, 0x72, 0x52, 0x0b, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73,
|
||||||
|
0x12, 0x18, 0x0a, 0x07, 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28,
|
||||||
|
0x08, 0x52, 0x07, 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x44, 0x6f,
|
||||||
|
0x6d, 0x61, 0x69, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x44, 0x6f, 0x6d,
|
||||||
|
0x61, 0x69, 0x6e, 0x73, 0x22, 0x48, 0x0a, 0x0a, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76,
|
||||||
|
0x65, 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x50, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02,
|
||||||
|
0x49, 0x50, 0x12, 0x16, 0x0a, 0x06, 0x4e, 0x53, 0x54, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01,
|
||||||
|
0x28, 0x03, 0x52, 0x06, 0x4e, 0x53, 0x54, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x50, 0x6f,
|
||||||
|
0x72, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x50, 0x6f, 0x72, 0x74, 0x32, 0xf7,
|
||||||
|
0x02, 0x0a, 0x11, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x72,
|
||||||
|
0x76, 0x69, 0x63, 0x65, 0x12, 0x45, 0x0a, 0x05, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x1c, 0x2e,
|
||||||
|
0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79,
|
||||||
|
0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61,
|
||||||
|
0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74,
|
||||||
|
0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x12, 0x46, 0x0a, 0x04, 0x53,
|
||||||
|
0x79, 0x6e, 0x63, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74,
|
||||||
0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67,
|
0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67,
|
||||||
0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45,
|
0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45,
|
||||||
0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22,
|
0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22,
|
||||||
0x00, 0x42, 0x08, 0x5a, 0x06, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f,
|
0x00, 0x30, 0x01, 0x12, 0x42, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72,
|
||||||
0x74, 0x6f, 0x33,
|
0x4b, 0x65, 0x79, 0x12, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74,
|
||||||
|
0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1d, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d,
|
||||||
|
0x65, 0x6e, 0x74, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73,
|
||||||
|
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x33, 0x0a, 0x09, 0x69, 0x73, 0x48, 0x65, 0x61,
|
||||||
|
0x6c, 0x74, 0x68, 0x79, 0x12, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e,
|
||||||
|
0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65,
|
||||||
|
0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x5a, 0x0a, 0x1a,
|
||||||
|
0x47, 0x65, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69,
|
||||||
|
0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e,
|
||||||
|
0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65,
|
||||||
|
0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67,
|
||||||
|
0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d,
|
||||||
|
0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x42, 0x08, 0x5a, 0x06, 0x2f, 0x70, 0x72, 0x6f,
|
||||||
|
0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -1692,7 +2069,7 @@ func file_management_proto_rawDescGZIP() []byte {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var file_management_proto_enumTypes = make([]protoimpl.EnumInfo, 2)
|
var file_management_proto_enumTypes = make([]protoimpl.EnumInfo, 2)
|
||||||
var file_management_proto_msgTypes = make([]protoimpl.MessageInfo, 20)
|
var file_management_proto_msgTypes = make([]protoimpl.MessageInfo, 25)
|
||||||
var file_management_proto_goTypes = []interface{}{
|
var file_management_proto_goTypes = []interface{}{
|
||||||
(HostConfig_Protocol)(0), // 0: management.HostConfig.Protocol
|
(HostConfig_Protocol)(0), // 0: management.HostConfig.Protocol
|
||||||
(DeviceAuthorizationFlowProvider)(0), // 1: management.DeviceAuthorizationFlow.provider
|
(DeviceAuthorizationFlowProvider)(0), // 1: management.DeviceAuthorizationFlow.provider
|
||||||
@@ -1716,7 +2093,12 @@ var file_management_proto_goTypes = []interface{}{
|
|||||||
(*DeviceAuthorizationFlow)(nil), // 19: management.DeviceAuthorizationFlow
|
(*DeviceAuthorizationFlow)(nil), // 19: management.DeviceAuthorizationFlow
|
||||||
(*ProviderConfig)(nil), // 20: management.ProviderConfig
|
(*ProviderConfig)(nil), // 20: management.ProviderConfig
|
||||||
(*Route)(nil), // 21: management.Route
|
(*Route)(nil), // 21: management.Route
|
||||||
(*timestamp.Timestamp)(nil), // 22: google.protobuf.Timestamp
|
(*DNSConfig)(nil), // 22: management.DNSConfig
|
||||||
|
(*CustomZone)(nil), // 23: management.CustomZone
|
||||||
|
(*SimpleRecord)(nil), // 24: management.SimpleRecord
|
||||||
|
(*NameServerGroup)(nil), // 25: management.NameServerGroup
|
||||||
|
(*NameServer)(nil), // 26: management.NameServer
|
||||||
|
(*timestamppb.Timestamp)(nil), // 27: google.protobuf.Timestamp
|
||||||
}
|
}
|
||||||
var file_management_proto_depIdxs = []int32{
|
var file_management_proto_depIdxs = []int32{
|
||||||
11, // 0: management.SyncResponse.wiretrusteeConfig:type_name -> management.WiretrusteeConfig
|
11, // 0: management.SyncResponse.wiretrusteeConfig:type_name -> management.WiretrusteeConfig
|
||||||
@@ -1727,7 +2109,7 @@ var file_management_proto_depIdxs = []int32{
|
|||||||
6, // 5: management.LoginRequest.peerKeys:type_name -> management.PeerKeys
|
6, // 5: management.LoginRequest.peerKeys:type_name -> management.PeerKeys
|
||||||
11, // 6: management.LoginResponse.wiretrusteeConfig:type_name -> management.WiretrusteeConfig
|
11, // 6: management.LoginResponse.wiretrusteeConfig:type_name -> management.WiretrusteeConfig
|
||||||
14, // 7: management.LoginResponse.peerConfig:type_name -> management.PeerConfig
|
14, // 7: management.LoginResponse.peerConfig:type_name -> management.PeerConfig
|
||||||
22, // 8: management.ServerKeyResponse.expiresAt:type_name -> google.protobuf.Timestamp
|
27, // 8: management.ServerKeyResponse.expiresAt:type_name -> google.protobuf.Timestamp
|
||||||
12, // 9: management.WiretrusteeConfig.stuns:type_name -> management.HostConfig
|
12, // 9: management.WiretrusteeConfig.stuns:type_name -> management.HostConfig
|
||||||
13, // 10: management.WiretrusteeConfig.turns:type_name -> management.ProtectedHostConfig
|
13, // 10: management.WiretrusteeConfig.turns:type_name -> management.ProtectedHostConfig
|
||||||
12, // 11: management.WiretrusteeConfig.signal:type_name -> management.HostConfig
|
12, // 11: management.WiretrusteeConfig.signal:type_name -> management.HostConfig
|
||||||
@@ -1737,24 +2119,29 @@ var file_management_proto_depIdxs = []int32{
|
|||||||
14, // 15: management.NetworkMap.peerConfig:type_name -> management.PeerConfig
|
14, // 15: management.NetworkMap.peerConfig:type_name -> management.PeerConfig
|
||||||
16, // 16: management.NetworkMap.remotePeers:type_name -> management.RemotePeerConfig
|
16, // 16: management.NetworkMap.remotePeers:type_name -> management.RemotePeerConfig
|
||||||
21, // 17: management.NetworkMap.Routes:type_name -> management.Route
|
21, // 17: management.NetworkMap.Routes:type_name -> management.Route
|
||||||
17, // 18: management.RemotePeerConfig.sshConfig:type_name -> management.SSHConfig
|
22, // 18: management.NetworkMap.DNSConfig:type_name -> management.DNSConfig
|
||||||
1, // 19: management.DeviceAuthorizationFlow.Provider:type_name -> management.DeviceAuthorizationFlow.provider
|
17, // 19: management.RemotePeerConfig.sshConfig:type_name -> management.SSHConfig
|
||||||
20, // 20: management.DeviceAuthorizationFlow.ProviderConfig:type_name -> management.ProviderConfig
|
1, // 20: management.DeviceAuthorizationFlow.Provider:type_name -> management.DeviceAuthorizationFlow.provider
|
||||||
2, // 21: management.ManagementService.Login:input_type -> management.EncryptedMessage
|
20, // 21: management.DeviceAuthorizationFlow.ProviderConfig:type_name -> management.ProviderConfig
|
||||||
2, // 22: management.ManagementService.Sync:input_type -> management.EncryptedMessage
|
25, // 22: management.DNSConfig.NameServerGroups:type_name -> management.NameServerGroup
|
||||||
10, // 23: management.ManagementService.GetServerKey:input_type -> management.Empty
|
23, // 23: management.DNSConfig.CustomZones:type_name -> management.CustomZone
|
||||||
10, // 24: management.ManagementService.isHealthy:input_type -> management.Empty
|
24, // 24: management.CustomZone.Records:type_name -> management.SimpleRecord
|
||||||
2, // 25: management.ManagementService.GetDeviceAuthorizationFlow:input_type -> management.EncryptedMessage
|
26, // 25: management.NameServerGroup.NameServers:type_name -> management.NameServer
|
||||||
2, // 26: management.ManagementService.Login:output_type -> management.EncryptedMessage
|
2, // 26: management.ManagementService.Login:input_type -> management.EncryptedMessage
|
||||||
2, // 27: management.ManagementService.Sync:output_type -> management.EncryptedMessage
|
2, // 27: management.ManagementService.Sync:input_type -> management.EncryptedMessage
|
||||||
9, // 28: management.ManagementService.GetServerKey:output_type -> management.ServerKeyResponse
|
10, // 28: management.ManagementService.GetServerKey:input_type -> management.Empty
|
||||||
10, // 29: management.ManagementService.isHealthy:output_type -> management.Empty
|
10, // 29: management.ManagementService.isHealthy:input_type -> management.Empty
|
||||||
2, // 30: management.ManagementService.GetDeviceAuthorizationFlow:output_type -> management.EncryptedMessage
|
2, // 30: management.ManagementService.GetDeviceAuthorizationFlow:input_type -> management.EncryptedMessage
|
||||||
26, // [26:31] is the sub-list for method output_type
|
2, // 31: management.ManagementService.Login:output_type -> management.EncryptedMessage
|
||||||
21, // [21:26] is the sub-list for method input_type
|
2, // 32: management.ManagementService.Sync:output_type -> management.EncryptedMessage
|
||||||
21, // [21:21] is the sub-list for extension type_name
|
9, // 33: management.ManagementService.GetServerKey:output_type -> management.ServerKeyResponse
|
||||||
21, // [21:21] is the sub-list for extension extendee
|
10, // 34: management.ManagementService.isHealthy:output_type -> management.Empty
|
||||||
0, // [0:21] is the sub-list for field type_name
|
2, // 35: management.ManagementService.GetDeviceAuthorizationFlow:output_type -> management.EncryptedMessage
|
||||||
|
31, // [31:36] is the sub-list for method output_type
|
||||||
|
26, // [26:31] is the sub-list for method input_type
|
||||||
|
26, // [26:26] is the sub-list for extension type_name
|
||||||
|
26, // [26:26] is the sub-list for extension extendee
|
||||||
|
0, // [0:26] is the sub-list for field type_name
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() { file_management_proto_init() }
|
func init() { file_management_proto_init() }
|
||||||
@@ -2003,6 +2390,66 @@ func file_management_proto_init() {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
file_management_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*DNSConfig); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_management_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*CustomZone); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_management_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*SimpleRecord); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_management_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*NameServerGroup); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_management_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*NameServer); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
type x struct{}
|
type x struct{}
|
||||||
out := protoimpl.TypeBuilder{
|
out := protoimpl.TypeBuilder{
|
||||||
@@ -2010,7 +2457,7 @@ func file_management_proto_init() {
|
|||||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
RawDescriptor: file_management_proto_rawDesc,
|
RawDescriptor: file_management_proto_rawDesc,
|
||||||
NumEnums: 2,
|
NumEnums: 2,
|
||||||
NumMessages: 20,
|
NumMessages: 25,
|
||||||
NumExtensions: 0,
|
NumExtensions: 0,
|
||||||
NumServices: 1,
|
NumServices: 1,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -178,6 +178,9 @@ message NetworkMap {
|
|||||||
|
|
||||||
// List of routes to be applied
|
// List of routes to be applied
|
||||||
repeated Route Routes = 5;
|
repeated Route Routes = 5;
|
||||||
|
|
||||||
|
// DNS config to be applied
|
||||||
|
DNSConfig DNSConfig = 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemotePeerConfig represents a configuration of a remote peer.
|
// RemotePeerConfig represents a configuration of a remote peer.
|
||||||
@@ -246,4 +249,40 @@ message Route {
|
|||||||
int64 Metric = 5;
|
int64 Metric = 5;
|
||||||
bool Masquerade = 6;
|
bool Masquerade = 6;
|
||||||
string NetID = 7;
|
string NetID = 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
// DNSConfig represents a dns.Update
|
||||||
|
message DNSConfig {
|
||||||
|
bool ServiceEnable = 1;
|
||||||
|
repeated NameServerGroup NameServerGroups = 2;
|
||||||
|
repeated CustomZone CustomZones = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
// CustomZone represents a dns.CustomZone
|
||||||
|
message CustomZone {
|
||||||
|
string Domain = 1;
|
||||||
|
repeated SimpleRecord Records = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// SimpleRecord represents a dns.SimpleRecord
|
||||||
|
message SimpleRecord {
|
||||||
|
string Name = 1;
|
||||||
|
int64 Type = 2;
|
||||||
|
string Class = 3;
|
||||||
|
int64 TTL = 4;
|
||||||
|
string RData = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
// NameServerGroup represents a dns.NameServerGroup
|
||||||
|
message NameServerGroup {
|
||||||
|
repeated NameServer NameServers = 1;
|
||||||
|
bool Primary = 2;
|
||||||
|
repeated string Domains = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
// NameServer represents a dns.NameServer
|
||||||
|
message NameServer {
|
||||||
|
string IP = 1;
|
||||||
|
int64 NSType = 2;
|
||||||
|
int64 Port = 3;
|
||||||
}
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,11 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
nbdns "github.com/netbirdio/netbird/dns"
|
||||||
|
"github.com/netbirdio/netbird/route"
|
||||||
"net"
|
"net"
|
||||||
|
"reflect"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@@ -117,7 +121,7 @@ func TestAccountManager_GetOrCreateAccountByUser(t *testing.T) {
|
|||||||
t.Fatalf("expected to create an account for a user %s", userId)
|
t.Fatalf("expected to create an account for a user %s", userId)
|
||||||
}
|
}
|
||||||
|
|
||||||
account, err = manager.GetAccountByUser(userId)
|
account, err = manager.Store.GetAccountByUser(userId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("expected to get existing account after creation, no account was found for a user %s", userId)
|
t.Errorf("expected to get existing account after creation, no account was found for a user %s", userId)
|
||||||
}
|
}
|
||||||
@@ -127,7 +131,7 @@ func TestAccountManager_GetOrCreateAccountByUser(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDefaultAccountManager_GetAccountWithAuthorizationClaims(t *testing.T) {
|
func TestDefaultAccountManager_GetAccountFromToken(t *testing.T) {
|
||||||
type initUserParams jwtclaims.AuthorizationClaims
|
type initUserParams jwtclaims.AuthorizationClaims
|
||||||
|
|
||||||
type test struct {
|
type test struct {
|
||||||
@@ -140,6 +144,7 @@ func TestDefaultAccountManager_GetAccountWithAuthorizationClaims(t *testing.T) {
|
|||||||
expectedMSG string
|
expectedMSG string
|
||||||
expectedUserRole UserRole
|
expectedUserRole UserRole
|
||||||
expectedDomainCategory string
|
expectedDomainCategory string
|
||||||
|
expectedDomain string
|
||||||
expectedPrimaryDomainStatus bool
|
expectedPrimaryDomainStatus bool
|
||||||
expectedCreatedBy string
|
expectedCreatedBy string
|
||||||
expectedUsers []string
|
expectedUsers []string
|
||||||
@@ -168,6 +173,7 @@ func TestDefaultAccountManager_GetAccountWithAuthorizationClaims(t *testing.T) {
|
|||||||
expectedMSG: "account IDs shouldn't match",
|
expectedMSG: "account IDs shouldn't match",
|
||||||
expectedUserRole: UserRoleAdmin,
|
expectedUserRole: UserRoleAdmin,
|
||||||
expectedDomainCategory: "",
|
expectedDomainCategory: "",
|
||||||
|
expectedDomain: publicDomain,
|
||||||
expectedPrimaryDomainStatus: false,
|
expectedPrimaryDomainStatus: false,
|
||||||
expectedCreatedBy: "pub-domain-user",
|
expectedCreatedBy: "pub-domain-user",
|
||||||
expectedUsers: []string{"pub-domain-user"},
|
expectedUsers: []string{"pub-domain-user"},
|
||||||
@@ -188,6 +194,7 @@ func TestDefaultAccountManager_GetAccountWithAuthorizationClaims(t *testing.T) {
|
|||||||
testingFunc: require.NotEqual,
|
testingFunc: require.NotEqual,
|
||||||
expectedMSG: "account IDs shouldn't match",
|
expectedMSG: "account IDs shouldn't match",
|
||||||
expectedUserRole: UserRoleAdmin,
|
expectedUserRole: UserRoleAdmin,
|
||||||
|
expectedDomain: unknownDomain,
|
||||||
expectedDomainCategory: "",
|
expectedDomainCategory: "",
|
||||||
expectedPrimaryDomainStatus: false,
|
expectedPrimaryDomainStatus: false,
|
||||||
expectedCreatedBy: "unknown-domain-user",
|
expectedCreatedBy: "unknown-domain-user",
|
||||||
@@ -205,6 +212,7 @@ func TestDefaultAccountManager_GetAccountWithAuthorizationClaims(t *testing.T) {
|
|||||||
testingFunc: require.NotEqual,
|
testingFunc: require.NotEqual,
|
||||||
expectedMSG: "account IDs shouldn't match",
|
expectedMSG: "account IDs shouldn't match",
|
||||||
expectedUserRole: UserRoleAdmin,
|
expectedUserRole: UserRoleAdmin,
|
||||||
|
expectedDomain: privateDomain,
|
||||||
expectedDomainCategory: PrivateCategory,
|
expectedDomainCategory: PrivateCategory,
|
||||||
expectedPrimaryDomainStatus: true,
|
expectedPrimaryDomainStatus: true,
|
||||||
expectedCreatedBy: "pvt-domain-user",
|
expectedCreatedBy: "pvt-domain-user",
|
||||||
@@ -227,6 +235,7 @@ func TestDefaultAccountManager_GetAccountWithAuthorizationClaims(t *testing.T) {
|
|||||||
testingFunc: require.Equal,
|
testingFunc: require.Equal,
|
||||||
expectedMSG: "account IDs should match",
|
expectedMSG: "account IDs should match",
|
||||||
expectedUserRole: UserRoleUser,
|
expectedUserRole: UserRoleUser,
|
||||||
|
expectedDomain: privateDomain,
|
||||||
expectedDomainCategory: PrivateCategory,
|
expectedDomainCategory: PrivateCategory,
|
||||||
expectedPrimaryDomainStatus: true,
|
expectedPrimaryDomainStatus: true,
|
||||||
expectedCreatedBy: defaultInitAccount.UserId,
|
expectedCreatedBy: defaultInitAccount.UserId,
|
||||||
@@ -244,6 +253,7 @@ func TestDefaultAccountManager_GetAccountWithAuthorizationClaims(t *testing.T) {
|
|||||||
testingFunc: require.Equal,
|
testingFunc: require.Equal,
|
||||||
expectedMSG: "account IDs should match",
|
expectedMSG: "account IDs should match",
|
||||||
expectedUserRole: UserRoleAdmin,
|
expectedUserRole: UserRoleAdmin,
|
||||||
|
expectedDomain: defaultInitAccount.Domain,
|
||||||
expectedDomainCategory: PrivateCategory,
|
expectedDomainCategory: PrivateCategory,
|
||||||
expectedPrimaryDomainStatus: true,
|
expectedPrimaryDomainStatus: true,
|
||||||
expectedCreatedBy: defaultInitAccount.UserId,
|
expectedCreatedBy: defaultInitAccount.UserId,
|
||||||
@@ -262,17 +272,37 @@ func TestDefaultAccountManager_GetAccountWithAuthorizationClaims(t *testing.T) {
|
|||||||
testingFunc: require.Equal,
|
testingFunc: require.Equal,
|
||||||
expectedMSG: "account IDs should match",
|
expectedMSG: "account IDs should match",
|
||||||
expectedUserRole: UserRoleAdmin,
|
expectedUserRole: UserRoleAdmin,
|
||||||
|
expectedDomain: defaultInitAccount.Domain,
|
||||||
expectedDomainCategory: PrivateCategory,
|
expectedDomainCategory: PrivateCategory,
|
||||||
expectedPrimaryDomainStatus: true,
|
expectedPrimaryDomainStatus: true,
|
||||||
expectedCreatedBy: defaultInitAccount.UserId,
|
expectedCreatedBy: defaultInitAccount.UserId,
|
||||||
expectedUsers: []string{defaultInitAccount.UserId},
|
expectedUsers: []string{defaultInitAccount.UserId},
|
||||||
}
|
}
|
||||||
for _, testCase := range []test{testCase1, testCase2, testCase3, testCase4, testCase5, testCase6} {
|
|
||||||
|
testCase7 := test{
|
||||||
|
name: "User With Private Category And Empty Domain",
|
||||||
|
inputClaims: jwtclaims.AuthorizationClaims{
|
||||||
|
Domain: "",
|
||||||
|
UserId: "pvt-domain-user",
|
||||||
|
DomainCategory: PrivateCategory,
|
||||||
|
},
|
||||||
|
inputInitUserParams: defaultInitAccount,
|
||||||
|
testingFunc: require.NotEqual,
|
||||||
|
expectedMSG: "account IDs shouldn't match",
|
||||||
|
expectedUserRole: UserRoleAdmin,
|
||||||
|
expectedDomain: "",
|
||||||
|
expectedDomainCategory: "",
|
||||||
|
expectedPrimaryDomainStatus: false,
|
||||||
|
expectedCreatedBy: "pvt-domain-user",
|
||||||
|
expectedUsers: []string{"pvt-domain-user"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, testCase := range []test{testCase1, testCase2, testCase3, testCase4, testCase5, testCase6, testCase7} {
|
||||||
t.Run(testCase.name, func(t *testing.T) {
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
manager, err := createManager(t)
|
manager, err := createManager(t)
|
||||||
require.NoError(t, err, "unable to create account manager")
|
require.NoError(t, err, "unable to create account manager")
|
||||||
|
|
||||||
initAccount, err := manager.GetAccountByUserOrAccountId(testCase.inputInitUserParams.UserId, testCase.inputInitUserParams.AccountId, testCase.inputInitUserParams.Domain)
|
initAccount, err := manager.GetAccountByUserOrAccountID(testCase.inputInitUserParams.UserId, testCase.inputInitUserParams.AccountId, testCase.inputInitUserParams.Domain)
|
||||||
require.NoError(t, err, "create init user failed")
|
require.NoError(t, err, "create init user failed")
|
||||||
|
|
||||||
if testCase.inputUpdateAttrs {
|
if testCase.inputUpdateAttrs {
|
||||||
@@ -284,7 +314,7 @@ func TestDefaultAccountManager_GetAccountWithAuthorizationClaims(t *testing.T) {
|
|||||||
testCase.inputClaims.AccountId = initAccount.Id
|
testCase.inputClaims.AccountId = initAccount.Id
|
||||||
}
|
}
|
||||||
|
|
||||||
account, err := manager.GetAccountWithAuthorizationClaims(testCase.inputClaims)
|
account, _, err := manager.GetAccountFromToken(testCase.inputClaims)
|
||||||
require.NoError(t, err, "support function failed")
|
require.NoError(t, err, "support function failed")
|
||||||
verifyNewAccountHasDefaultFields(t, account, testCase.expectedCreatedBy, testCase.inputClaims.Domain, testCase.expectedUsers)
|
verifyNewAccountHasDefaultFields(t, account, testCase.expectedCreatedBy, testCase.inputClaims.Domain, testCase.expectedUsers)
|
||||||
verifyCanAddPeerToAccount(t, manager, account, testCase.expectedCreatedBy)
|
verifyCanAddPeerToAccount(t, manager, account, testCase.expectedCreatedBy)
|
||||||
@@ -294,6 +324,7 @@ func TestDefaultAccountManager_GetAccountWithAuthorizationClaims(t *testing.T) {
|
|||||||
require.EqualValues(t, testCase.expectedUserRole, account.Users[testCase.inputClaims.UserId].Role, "expected user role should match")
|
require.EqualValues(t, testCase.expectedUserRole, account.Users[testCase.inputClaims.UserId].Role, "expected user role should match")
|
||||||
require.EqualValues(t, testCase.expectedDomainCategory, account.DomainCategory, "expected account domain category should match")
|
require.EqualValues(t, testCase.expectedDomainCategory, account.DomainCategory, "expected account domain category should match")
|
||||||
require.EqualValues(t, testCase.expectedPrimaryDomainStatus, account.IsDomainPrimaryAccount, "expected account primary status should match")
|
require.EqualValues(t, testCase.expectedPrimaryDomainStatus, account.IsDomainPrimaryAccount, "expected account primary status should match")
|
||||||
|
require.EqualValues(t, testCase.expectedDomain, account.Domain, "expected account domain should match")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -314,7 +345,7 @@ func TestAccountManager_PrivateAccount(t *testing.T) {
|
|||||||
t.Fatalf("expected to create an account for a user %s", userId)
|
t.Fatalf("expected to create an account for a user %s", userId)
|
||||||
}
|
}
|
||||||
|
|
||||||
account, err = manager.GetAccountByUser(userId)
|
account, err = manager.Store.GetAccountByUser(userId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("expected to get existing account after creation, no account was found for a user %s", userId)
|
t.Errorf("expected to get existing account after creation, no account was found for a user %s", userId)
|
||||||
}
|
}
|
||||||
@@ -370,7 +401,7 @@ func TestAccountManager_GetAccountByUserOrAccountId(t *testing.T) {
|
|||||||
|
|
||||||
userId := "test_user"
|
userId := "test_user"
|
||||||
|
|
||||||
account, err := manager.GetAccountByUserOrAccountId(userId, "", "")
|
account, err := manager.GetAccountByUserOrAccountID(userId, "", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -380,12 +411,12 @@ func TestAccountManager_GetAccountByUserOrAccountId(t *testing.T) {
|
|||||||
|
|
||||||
accountId := account.Id
|
accountId := account.Id
|
||||||
|
|
||||||
_, err = manager.GetAccountByUserOrAccountId("", accountId, "")
|
_, err = manager.GetAccountByUserOrAccountID("", accountId, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("expected to get existing account after creation using userid, no account was found for a account %s", accountId)
|
t.Errorf("expected to get existing account after creation using userid, no account was found for a account %s", accountId)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = manager.GetAccountByUserOrAccountId("", "", "")
|
_, err = manager.GetAccountByUserOrAccountID("", "", "")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Errorf("expected an error when user and account IDs are empty")
|
t.Errorf("expected an error when user and account IDs are empty")
|
||||||
}
|
}
|
||||||
@@ -439,7 +470,7 @@ func TestAccountManager_GetAccount(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// AddAccount has been already tested so we can assume it is correct and compare results
|
// AddAccount has been already tested so we can assume it is correct and compare results
|
||||||
getAccount, err := manager.GetAccountById(expectedId)
|
getAccount, err := manager.Store.GetAccount(account.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
return
|
return
|
||||||
@@ -509,7 +540,7 @@ func TestAccountManager_AddPeer(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
account, err = manager.GetAccountById(account.Id)
|
account, err = manager.Store.GetAccount(account.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
return
|
return
|
||||||
@@ -571,7 +602,7 @@ func TestAccountManager_AddPeerWithUserID(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
account, err = manager.GetAccountById(account.Id)
|
account, err = manager.Store.GetAccount(account.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
return
|
return
|
||||||
@@ -649,7 +680,7 @@ func TestAccountManager_NetworkUpdates(t *testing.T) {
|
|||||||
peer2 := getPeer()
|
peer2 := getPeer()
|
||||||
peer3 := getPeer()
|
peer3 := getPeer()
|
||||||
|
|
||||||
account, err = manager.GetAccountById(account.Id)
|
account, err = manager.Store.GetAccount(account.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
return
|
return
|
||||||
@@ -817,7 +848,7 @@ func TestAccountManager_DeletePeer(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
account, err = manager.GetAccountById(account.Id)
|
account, err = manager.Store.GetAccount(account.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
return
|
return
|
||||||
@@ -847,7 +878,7 @@ func TestGetUsersFromAccount(t *testing.T) {
|
|||||||
account.Users[user.Id] = user
|
account.Users[user.Id] = user
|
||||||
}
|
}
|
||||||
|
|
||||||
userInfos, err := manager.GetUsersFromAccount(accountId)
|
userInfos, err := manager.GetUsersFromAccount(accountId, "1")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -930,17 +961,257 @@ func TestAccountManager_UpdatePeerMeta(t *testing.T) {
|
|||||||
assert.Equal(t, newMeta, p.Meta)
|
assert.Equal(t, newMeta, p.Meta)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAccount_GetPeerRules(t *testing.T) {
|
||||||
|
|
||||||
|
groups := map[string]*Group{
|
||||||
|
"group_1": {
|
||||||
|
ID: "group_1",
|
||||||
|
Name: "group_1",
|
||||||
|
Peers: []string{"peer-1", "peer-2"},
|
||||||
|
},
|
||||||
|
"group_2": {
|
||||||
|
ID: "group_2",
|
||||||
|
Name: "group_2",
|
||||||
|
Peers: []string{"peer-2", "peer-3"},
|
||||||
|
},
|
||||||
|
"group_3": {
|
||||||
|
ID: "group_3",
|
||||||
|
Name: "group_3",
|
||||||
|
Peers: []string{"peer-4"},
|
||||||
|
},
|
||||||
|
"group_4": {
|
||||||
|
ID: "group_4",
|
||||||
|
Name: "group_4",
|
||||||
|
Peers: []string{"peer-1"},
|
||||||
|
},
|
||||||
|
"group_5": {
|
||||||
|
ID: "group_5",
|
||||||
|
Name: "group_5",
|
||||||
|
Peers: []string{"peer-1"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
rules := map[string]*Rule{
|
||||||
|
"rule-1": {
|
||||||
|
ID: "rule-1",
|
||||||
|
Name: "rule-1",
|
||||||
|
Description: "rule-1",
|
||||||
|
Disabled: false,
|
||||||
|
Source: []string{"group_1", "group_5"},
|
||||||
|
Destination: []string{"group_2"},
|
||||||
|
Flow: 0,
|
||||||
|
},
|
||||||
|
"rule-2": {
|
||||||
|
ID: "rule-2",
|
||||||
|
Name: "rule-2",
|
||||||
|
Description: "rule-2",
|
||||||
|
Disabled: false,
|
||||||
|
Source: []string{"group_1"},
|
||||||
|
Destination: []string{"group_1"},
|
||||||
|
Flow: 0,
|
||||||
|
},
|
||||||
|
"rule-3": {
|
||||||
|
ID: "rule-3",
|
||||||
|
Name: "rule-3",
|
||||||
|
Description: "rule-3",
|
||||||
|
Disabled: false,
|
||||||
|
Source: []string{"group_3"},
|
||||||
|
Destination: []string{"group_3"},
|
||||||
|
Flow: 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
account := &Account{
|
||||||
|
Groups: groups,
|
||||||
|
Rules: rules,
|
||||||
|
}
|
||||||
|
|
||||||
|
srcRules, dstRules := account.GetPeerRules("peer-1")
|
||||||
|
|
||||||
|
assert.Equal(t, 2, len(srcRules))
|
||||||
|
assert.Equal(t, 1, len(dstRules))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileStore_GetRoutesByPrefix(t *testing.T) {
|
||||||
|
_, prefix, err := route.ParseNetwork("192.168.64.0/24")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
account := &Account{
|
||||||
|
Routes: map[string]*route.Route{
|
||||||
|
"route-1": {
|
||||||
|
ID: "route-1",
|
||||||
|
Network: prefix,
|
||||||
|
NetID: "network-1",
|
||||||
|
Description: "network-1",
|
||||||
|
Peer: "peer-1",
|
||||||
|
NetworkType: 0,
|
||||||
|
Masquerade: false,
|
||||||
|
Metric: 999,
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
"route-2": {
|
||||||
|
ID: "route-2",
|
||||||
|
Network: prefix,
|
||||||
|
NetID: "network-1",
|
||||||
|
Description: "network-1",
|
||||||
|
Peer: "peer-2",
|
||||||
|
NetworkType: 0,
|
||||||
|
Masquerade: false,
|
||||||
|
Metric: 999,
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
routes := account.GetRoutesByPrefix(prefix)
|
||||||
|
|
||||||
|
assert.Len(t, routes, 2)
|
||||||
|
routeIDs := make(map[string]struct{}, 2)
|
||||||
|
for _, r := range routes {
|
||||||
|
routeIDs[r.ID] = struct{}{}
|
||||||
|
}
|
||||||
|
assert.Contains(t, routeIDs, "route-1")
|
||||||
|
assert.Contains(t, routeIDs, "route-2")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccount_GetPeersRoutes(t *testing.T) {
|
||||||
|
_, prefix, err := route.ParseNetwork("192.168.64.0/24")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
account := &Account{
|
||||||
|
Peers: map[string]*Peer{
|
||||||
|
"peer-1": {Key: "peer-1"}, "peer-2": {Key: "peer-2"}, "peer-3": {Key: "peer-1"},
|
||||||
|
},
|
||||||
|
Routes: map[string]*route.Route{
|
||||||
|
"route-1": {
|
||||||
|
ID: "route-1",
|
||||||
|
Network: prefix,
|
||||||
|
NetID: "network-1",
|
||||||
|
Description: "network-1",
|
||||||
|
Peer: "peer-1",
|
||||||
|
NetworkType: 0,
|
||||||
|
Masquerade: false,
|
||||||
|
Metric: 999,
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
"route-2": {
|
||||||
|
ID: "route-2",
|
||||||
|
Network: prefix,
|
||||||
|
NetID: "network-1",
|
||||||
|
Description: "network-1",
|
||||||
|
Peer: "peer-2",
|
||||||
|
NetworkType: 0,
|
||||||
|
Masquerade: false,
|
||||||
|
Metric: 999,
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
routes := account.GetPeersRoutes([]*Peer{{Key: "peer-1"}, {Key: "peer-2"}, {Key: "non-existing-peer"}})
|
||||||
|
|
||||||
|
assert.Len(t, routes, 2)
|
||||||
|
routeIDs := make(map[string]struct{}, 2)
|
||||||
|
for _, r := range routes {
|
||||||
|
routeIDs[r.ID] = struct{}{}
|
||||||
|
}
|
||||||
|
assert.Contains(t, routeIDs, "route-1")
|
||||||
|
assert.Contains(t, routeIDs, "route-2")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccount_Copy(t *testing.T) {
|
||||||
|
account := &Account{
|
||||||
|
Id: "account1",
|
||||||
|
CreatedBy: "tester",
|
||||||
|
Domain: "test.com",
|
||||||
|
DomainCategory: "public",
|
||||||
|
IsDomainPrimaryAccount: true,
|
||||||
|
SetupKeys: map[string]*SetupKey{
|
||||||
|
"setup1": {
|
||||||
|
Id: "setup1",
|
||||||
|
AutoGroups: []string{"group1"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Network: &Network{
|
||||||
|
Id: "net1",
|
||||||
|
},
|
||||||
|
Peers: map[string]*Peer{
|
||||||
|
"peer1": {
|
||||||
|
Key: "key1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Users: map[string]*User{
|
||||||
|
"user1": {
|
||||||
|
Id: "user1",
|
||||||
|
Role: UserRoleAdmin,
|
||||||
|
AutoGroups: []string{"group1"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Groups: map[string]*Group{
|
||||||
|
"group1": {
|
||||||
|
ID: "group1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Rules: map[string]*Rule{
|
||||||
|
"rule1": {
|
||||||
|
ID: "rule1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Routes: map[string]*route.Route{
|
||||||
|
"route1": {
|
||||||
|
ID: "route1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
NameServerGroups: map[string]*nbdns.NameServerGroup{
|
||||||
|
"nsGroup1": {
|
||||||
|
ID: "nsGroup1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
err := hasNilField(account)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
accountCopy := account.Copy()
|
||||||
|
assert.Equal(t, account, accountCopy, "account copy returned a different value than expected")
|
||||||
|
}
|
||||||
|
|
||||||
|
// hasNilField validates pointers, maps and slices if they are nil
|
||||||
|
func hasNilField(x interface{}) error {
|
||||||
|
rv := reflect.ValueOf(x)
|
||||||
|
rv = rv.Elem()
|
||||||
|
for i := 0; i < rv.NumField(); i++ {
|
||||||
|
if f := rv.Field(i); f.IsValid() {
|
||||||
|
k := f.Kind()
|
||||||
|
switch k {
|
||||||
|
case reflect.Ptr:
|
||||||
|
if f.IsNil() {
|
||||||
|
return fmt.Errorf("field %s is nil", f.String())
|
||||||
|
}
|
||||||
|
case reflect.Map, reflect.Slice:
|
||||||
|
if f.Len() == 0 || f.IsNil() {
|
||||||
|
return fmt.Errorf("field %s is nil", f.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func createManager(t *testing.T) (*DefaultAccountManager, error) {
|
func createManager(t *testing.T) (*DefaultAccountManager, error) {
|
||||||
store, err := createStore(t)
|
store, err := createStore(t)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return BuildManager(store, NewPeersUpdateManager(), nil)
|
return BuildManager(store, NewPeersUpdateManager(), nil, "", "")
|
||||||
}
|
}
|
||||||
|
|
||||||
func createStore(t *testing.T) (Store, error) {
|
func createStore(t *testing.T) (Store, error) {
|
||||||
dataDir := t.TempDir()
|
dataDir := t.TempDir()
|
||||||
store, err := NewStore(dataDir)
|
store, err := NewFileStore(dataDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
152
management/server/dns.go
Normal file
152
management/server/dns.go
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
nbdns "github.com/netbirdio/netbird/dns"
|
||||||
|
"github.com/netbirdio/netbird/management/proto"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type lookupMap map[string]struct{}
|
||||||
|
|
||||||
|
const defaultTTL = 300
|
||||||
|
|
||||||
|
func toProtocolDNSConfig(update nbdns.Config) *proto.DNSConfig {
|
||||||
|
protoUpdate := &proto.DNSConfig{ServiceEnable: update.ServiceEnable}
|
||||||
|
|
||||||
|
for _, zone := range update.CustomZones {
|
||||||
|
protoZone := &proto.CustomZone{Domain: zone.Domain}
|
||||||
|
for _, record := range zone.Records {
|
||||||
|
protoZone.Records = append(protoZone.Records, &proto.SimpleRecord{
|
||||||
|
Name: record.Name,
|
||||||
|
Type: int64(record.Type),
|
||||||
|
Class: record.Class,
|
||||||
|
TTL: int64(record.TTL),
|
||||||
|
RData: record.RData,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
protoUpdate.CustomZones = append(protoUpdate.CustomZones, protoZone)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, nsGroup := range update.NameServerGroups {
|
||||||
|
protoGroup := &proto.NameServerGroup{
|
||||||
|
Primary: nsGroup.Primary,
|
||||||
|
Domains: nsGroup.Domains,
|
||||||
|
}
|
||||||
|
for _, ns := range nsGroup.NameServers {
|
||||||
|
protoNS := &proto.NameServer{
|
||||||
|
IP: ns.IP.String(),
|
||||||
|
Port: int64(ns.Port),
|
||||||
|
NSType: int64(ns.NSType),
|
||||||
|
}
|
||||||
|
protoGroup.NameServers = append(protoGroup.NameServers, protoNS)
|
||||||
|
}
|
||||||
|
protoUpdate.NameServerGroups = append(protoUpdate.NameServerGroups, protoGroup)
|
||||||
|
}
|
||||||
|
|
||||||
|
return protoUpdate
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPeersCustomZone(account *Account, dnsDomain string) nbdns.CustomZone {
|
||||||
|
if dnsDomain == "" {
|
||||||
|
log.Errorf("no dns domain is set, returning empty zone")
|
||||||
|
return nbdns.CustomZone{}
|
||||||
|
}
|
||||||
|
|
||||||
|
customZone := nbdns.CustomZone{
|
||||||
|
Domain: dns.Fqdn(dnsDomain),
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, peer := range account.Peers {
|
||||||
|
if peer.DNSLabel == "" {
|
||||||
|
log.Errorf("found a peer with empty dns label. It was probably caused by a invalid character in its name. Peer Name: %s", peer.Name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
customZone.Records = append(customZone.Records, nbdns.SimpleRecord{
|
||||||
|
Name: dns.Fqdn(peer.DNSLabel + "." + dnsDomain),
|
||||||
|
Type: int(dns.TypeA),
|
||||||
|
Class: nbdns.DefaultClass,
|
||||||
|
TTL: defaultTTL,
|
||||||
|
RData: peer.IP.String(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return customZone
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPeerNSGroups(account *Account, peerID string) []*nbdns.NameServerGroup {
|
||||||
|
groupList := make(lookupMap)
|
||||||
|
for groupID, group := range account.Groups {
|
||||||
|
for _, id := range group.Peers {
|
||||||
|
if id == peerID {
|
||||||
|
groupList[groupID] = struct{}{}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var peerNSGroups []*nbdns.NameServerGroup
|
||||||
|
|
||||||
|
for _, nsGroup := range account.NameServerGroups {
|
||||||
|
if !nsGroup.Enabled {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, gID := range nsGroup.Groups {
|
||||||
|
_, found := groupList[gID]
|
||||||
|
if found {
|
||||||
|
peerNSGroups = append(peerNSGroups, nsGroup.Copy())
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return peerNSGroups
|
||||||
|
}
|
||||||
|
|
||||||
|
func addPeerLabelsToAccount(account *Account, peerLabels lookupMap) {
|
||||||
|
for _, peer := range account.Peers {
|
||||||
|
label, err := getPeerHostLabel(peer.Name, peerLabels)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("got an error while generating a peer host label. Peer name %s, error: %v. Trying with the peer's meta hostname", peer.Name, err)
|
||||||
|
label, err = getPeerHostLabel(peer.Meta.Hostname, peerLabels)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("got another error while generating a peer host label with hostname. Peer hostname %s, error: %v. Skiping", peer.Meta.Hostname, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
peer.DNSLabel = label
|
||||||
|
peerLabels[label] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPeerHostLabel(name string, peerLabels lookupMap) (string, error) {
|
||||||
|
label, err := nbdns.GetParsedDomainLabel(name)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
uniqueLabel := getUniqueHostLabel(label, peerLabels)
|
||||||
|
if uniqueLabel == "" {
|
||||||
|
return "", fmt.Errorf("couldn't find a unique valid label for %s, parsed label %s", name, label)
|
||||||
|
}
|
||||||
|
return uniqueLabel, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getUniqueHostLabel look for a unique host label, and if doesn't find add a suffix up to 999
|
||||||
|
func getUniqueHostLabel(name string, peerLabels lookupMap) string {
|
||||||
|
_, found := peerLabels[name]
|
||||||
|
if !found {
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
for i := 1; i < 1000; i++ {
|
||||||
|
nameWithSuffix := name + "-" + strconv.Itoa(i)
|
||||||
|
_, found = peerLabels[nameWithSuffix]
|
||||||
|
if !found {
|
||||||
|
return nameWithSuffix
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
@@ -1,16 +1,13 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"github.com/netbirdio/netbird/management/server/status"
|
||||||
"github.com/netbirdio/netbird/route"
|
log "github.com/sirupsen/logrus"
|
||||||
"net/netip"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
"google.golang.org/grpc/codes"
|
|
||||||
"google.golang.org/grpc/status"
|
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/util"
|
"github.com/netbirdio/netbird/util"
|
||||||
)
|
)
|
||||||
@@ -21,28 +18,29 @@ const storeFileName = "store.json"
|
|||||||
// FileStore represents an account storage backed by a file persisted to disk
|
// FileStore represents an account storage backed by a file persisted to disk
|
||||||
type FileStore struct {
|
type FileStore struct {
|
||||||
Accounts map[string]*Account
|
Accounts map[string]*Account
|
||||||
SetupKeyId2AccountId map[string]string `json:"-"`
|
SetupKeyID2AccountID map[string]string `json:"-"`
|
||||||
PeerKeyId2AccountId map[string]string `json:"-"`
|
PeerKeyID2AccountID map[string]string `json:"-"`
|
||||||
UserId2AccountId map[string]string `json:"-"`
|
UserID2AccountID map[string]string `json:"-"`
|
||||||
PrivateDomain2AccountId map[string]string `json:"-"`
|
PrivateDomain2AccountID map[string]string `json:"-"`
|
||||||
PeerKeyId2SrcRulesId map[string]map[string]struct{} `json:"-"`
|
InstallationID string
|
||||||
PeerKeyId2DstRulesId map[string]map[string]struct{} `json:"-"`
|
|
||||||
PeerKeyID2RouteIDs map[string]map[string]struct{} `json:"-"`
|
|
||||||
AccountPrefix2RouteIDs map[string]map[string][]string `json:"-"`
|
|
||||||
|
|
||||||
// mutex to synchronise Store read/write operations
|
// mutex to synchronise Store read/write operations
|
||||||
mux sync.Mutex `json:"-"`
|
mux sync.Mutex `json:"-"`
|
||||||
storeFile string `json:"-"`
|
storeFile string `json:"-"`
|
||||||
|
|
||||||
|
// sync.Mutex indexed by accountID
|
||||||
|
accountLocks sync.Map `json:"-"`
|
||||||
|
globalAccountLock sync.Mutex `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type StoredAccount struct{}
|
type StoredAccount struct{}
|
||||||
|
|
||||||
// NewStore restores a store from the file located in the datadir
|
// NewFileStore restores a store from the file located in the datadir
|
||||||
func NewStore(dataDir string) (*FileStore, error) {
|
func NewFileStore(dataDir string) (*FileStore, error) {
|
||||||
return restore(filepath.Join(dataDir, storeFileName))
|
return restore(filepath.Join(dataDir, storeFileName))
|
||||||
}
|
}
|
||||||
|
|
||||||
// restore restores the state of the store from the file.
|
// restore the state of the store from the file.
|
||||||
// Creates a new empty store file if doesn't exist
|
// Creates a new empty store file if doesn't exist
|
||||||
func restore(file string) (*FileStore, error) {
|
func restore(file string) (*FileStore, error) {
|
||||||
if _, err := os.Stat(file); os.IsNotExist(err) {
|
if _, err := os.Stat(file); os.IsNotExist(err) {
|
||||||
@@ -50,14 +48,11 @@ func restore(file string) (*FileStore, error) {
|
|||||||
s := &FileStore{
|
s := &FileStore{
|
||||||
Accounts: make(map[string]*Account),
|
Accounts: make(map[string]*Account),
|
||||||
mux: sync.Mutex{},
|
mux: sync.Mutex{},
|
||||||
SetupKeyId2AccountId: make(map[string]string),
|
globalAccountLock: sync.Mutex{},
|
||||||
PeerKeyId2AccountId: make(map[string]string),
|
SetupKeyID2AccountID: make(map[string]string),
|
||||||
UserId2AccountId: make(map[string]string),
|
PeerKeyID2AccountID: make(map[string]string),
|
||||||
PrivateDomain2AccountId: make(map[string]string),
|
UserID2AccountID: make(map[string]string),
|
||||||
PeerKeyId2SrcRulesId: make(map[string]map[string]struct{}),
|
PrivateDomain2AccountID: make(map[string]string),
|
||||||
PeerKeyID2RouteIDs: make(map[string]map[string]struct{}),
|
|
||||||
PeerKeyId2DstRulesId: make(map[string]map[string]struct{}),
|
|
||||||
AccountPrefix2RouteIDs: make(map[string]map[string][]string),
|
|
||||||
storeFile: file,
|
storeFile: file,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,287 +71,113 @@ func restore(file string) (*FileStore, error) {
|
|||||||
|
|
||||||
store := read.(*FileStore)
|
store := read.(*FileStore)
|
||||||
store.storeFile = file
|
store.storeFile = file
|
||||||
store.SetupKeyId2AccountId = make(map[string]string)
|
store.SetupKeyID2AccountID = make(map[string]string)
|
||||||
store.PeerKeyId2AccountId = make(map[string]string)
|
store.PeerKeyID2AccountID = make(map[string]string)
|
||||||
store.UserId2AccountId = make(map[string]string)
|
store.UserID2AccountID = make(map[string]string)
|
||||||
store.PrivateDomain2AccountId = make(map[string]string)
|
store.PrivateDomain2AccountID = make(map[string]string)
|
||||||
store.PeerKeyId2SrcRulesId = make(map[string]map[string]struct{})
|
|
||||||
store.PeerKeyId2DstRulesId = make(map[string]map[string]struct{})
|
|
||||||
store.PeerKeyID2RouteIDs = make(map[string]map[string]struct{})
|
|
||||||
store.AccountPrefix2RouteIDs = make(map[string]map[string][]string)
|
|
||||||
|
|
||||||
for accountId, account := range store.Accounts {
|
for accountID, account := range store.Accounts {
|
||||||
for setupKeyId := range account.SetupKeys {
|
for setupKeyId := range account.SetupKeys {
|
||||||
store.SetupKeyId2AccountId[strings.ToUpper(setupKeyId)] = accountId
|
store.SetupKeyID2AccountID[strings.ToUpper(setupKeyId)] = accountID
|
||||||
}
|
|
||||||
for _, rule := range account.Rules {
|
|
||||||
for _, groupID := range rule.Source {
|
|
||||||
if group, ok := account.Groups[groupID]; ok {
|
|
||||||
for _, peerID := range group.Peers {
|
|
||||||
rules := store.PeerKeyId2SrcRulesId[peerID]
|
|
||||||
if rules == nil {
|
|
||||||
rules = map[string]struct{}{}
|
|
||||||
store.PeerKeyId2SrcRulesId[peerID] = rules
|
|
||||||
}
|
|
||||||
rules[rule.ID] = struct{}{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, groupID := range rule.Destination {
|
|
||||||
if group, ok := account.Groups[groupID]; ok {
|
|
||||||
for _, peerID := range group.Peers {
|
|
||||||
rules := store.PeerKeyId2DstRulesId[peerID]
|
|
||||||
if rules == nil {
|
|
||||||
rules = map[string]struct{}{}
|
|
||||||
store.PeerKeyId2DstRulesId[peerID] = rules
|
|
||||||
}
|
|
||||||
rules[rule.ID] = struct{}{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, peer := range account.Peers {
|
for _, peer := range account.Peers {
|
||||||
store.PeerKeyId2AccountId[peer.Key] = accountId
|
store.PeerKeyID2AccountID[peer.Key] = accountID
|
||||||
|
// reset all peers to status = Disconnected
|
||||||
|
if peer.Status != nil && peer.Status.Connected {
|
||||||
|
peer.Status.Connected = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for _, user := range account.Users {
|
for _, user := range account.Users {
|
||||||
store.UserId2AccountId[user.Id] = accountId
|
store.UserID2AccountID[user.Id] = accountID
|
||||||
}
|
}
|
||||||
for _, user := range account.Users {
|
for _, user := range account.Users {
|
||||||
store.UserId2AccountId[user.Id] = accountId
|
store.UserID2AccountID[user.Id] = accountID
|
||||||
}
|
|
||||||
for _, route := range account.Routes {
|
|
||||||
if route.Peer == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if store.PeerKeyID2RouteIDs[route.Peer] == nil {
|
|
||||||
store.PeerKeyID2RouteIDs[route.Peer] = make(map[string]struct{})
|
|
||||||
}
|
|
||||||
store.PeerKeyID2RouteIDs[route.Peer][route.ID] = struct{}{}
|
|
||||||
if store.AccountPrefix2RouteIDs[account.Id] == nil {
|
|
||||||
store.AccountPrefix2RouteIDs[account.Id] = make(map[string][]string)
|
|
||||||
}
|
|
||||||
if _, ok := store.AccountPrefix2RouteIDs[account.Id][route.Network.String()]; !ok {
|
|
||||||
store.AccountPrefix2RouteIDs[account.Id][route.Network.String()] = make([]string, 0)
|
|
||||||
}
|
|
||||||
store.AccountPrefix2RouteIDs[account.Id][route.Network.String()] = append(
|
|
||||||
store.AccountPrefix2RouteIDs[account.Id][route.Network.String()],
|
|
||||||
route.ID,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if account.Domain != "" && account.DomainCategory == PrivateCategory &&
|
if account.Domain != "" && account.DomainCategory == PrivateCategory &&
|
||||||
account.IsDomainPrimaryAccount {
|
account.IsDomainPrimaryAccount {
|
||||||
store.PrivateDomain2AccountId[account.Domain] = accountId
|
store.PrivateDomain2AccountID[account.Domain] = accountID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// for data migration. Can be removed once most base will be with labels
|
||||||
|
existingLabels := account.getPeerDNSLabels()
|
||||||
|
if len(existingLabels) != len(account.Peers) {
|
||||||
|
addPeerLabelsToAccount(account, existingLabels)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// we need this persist to apply changes we made to account.Peers (we set them to Disconnected)
|
||||||
|
err = store.persist(store.storeFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return store, nil
|
return store, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// persist persists account data to a file
|
// persist account data to a file
|
||||||
// It is recommended to call it with locking FileStore.mux
|
// It is recommended to call it with locking FileStore.mux
|
||||||
func (s *FileStore) persist(file string) error {
|
func (s *FileStore) persist(file string) error {
|
||||||
return util.WriteJson(file, s)
|
return util.WriteJson(file, s)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SavePeer saves updated peer
|
// AcquireGlobalLock acquires global lock across all the accounts and returns a function that releases the lock
|
||||||
func (s *FileStore) SavePeer(accountId string, peer *Peer) error {
|
func (s *FileStore) AcquireGlobalLock() (unlock func()) {
|
||||||
s.mux.Lock()
|
log.Debugf("acquiring global lock")
|
||||||
defer s.mux.Unlock()
|
start := time.Now()
|
||||||
|
s.globalAccountLock.Lock()
|
||||||
|
|
||||||
account, err := s.GetAccount(accountId)
|
unlock = func() {
|
||||||
if err != nil {
|
s.globalAccountLock.Unlock()
|
||||||
return err
|
log.Debugf("released global lock in %v", time.Since(start))
|
||||||
}
|
}
|
||||||
|
|
||||||
// if it is new peer, add it to default 'All' group
|
return unlock
|
||||||
allGroup, err := account.GetGroupAll()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
ind := -1
|
|
||||||
for i, pid := range allGroup.Peers {
|
|
||||||
if pid == peer.Key {
|
|
||||||
ind = i
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ind < 0 {
|
|
||||||
allGroup.Peers = append(allGroup.Peers, peer.Key)
|
|
||||||
}
|
|
||||||
|
|
||||||
account.Peers[peer.Key] = peer
|
|
||||||
return s.persist(s.storeFile)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeletePeer deletes peer from the Store
|
// AcquireAccountLock acquires account lock and returns a function that releases the lock
|
||||||
func (s *FileStore) DeletePeer(accountId string, peerKey string) (*Peer, error) {
|
func (s *FileStore) AcquireAccountLock(accountID string) (unlock func()) {
|
||||||
s.mux.Lock()
|
log.Debugf("acquiring lock for account %s", accountID)
|
||||||
defer s.mux.Unlock()
|
start := time.Now()
|
||||||
|
value, _ := s.accountLocks.LoadOrStore(accountID, &sync.Mutex{})
|
||||||
|
mtx := value.(*sync.Mutex)
|
||||||
|
mtx.Lock()
|
||||||
|
|
||||||
account, err := s.GetAccount(accountId)
|
unlock = func() {
|
||||||
if err != nil {
|
mtx.Unlock()
|
||||||
return nil, err
|
log.Debugf("released lock for account %s in %v", accountID, time.Since(start))
|
||||||
}
|
}
|
||||||
|
|
||||||
peer := account.Peers[peerKey]
|
return unlock
|
||||||
if peer == nil {
|
|
||||||
return nil, status.Errorf(codes.NotFound, "peer not found")
|
|
||||||
}
|
|
||||||
peerRoutes := s.PeerKeyID2RouteIDs[peerKey]
|
|
||||||
delete(account.Peers, peerKey)
|
|
||||||
delete(s.PeerKeyId2AccountId, peerKey)
|
|
||||||
delete(s.PeerKeyId2DstRulesId, peerKey)
|
|
||||||
delete(s.PeerKeyId2SrcRulesId, peerKey)
|
|
||||||
delete(s.PeerKeyID2RouteIDs, peerKey)
|
|
||||||
|
|
||||||
// cleanup groups
|
|
||||||
for _, g := range account.Groups {
|
|
||||||
var peers []string
|
|
||||||
for _, p := range g.Peers {
|
|
||||||
if p != peerKey {
|
|
||||||
peers = append(peers, p)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
g.Peers = peers
|
|
||||||
}
|
|
||||||
|
|
||||||
for routeID := range peerRoutes {
|
|
||||||
account.Routes[routeID].Enabled = false
|
|
||||||
account.Routes[routeID].Peer = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
err = s.persist(s.storeFile)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return peer, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPeer returns a peer from a Store
|
|
||||||
func (s *FileStore) GetPeer(peerKey string) (*Peer, error) {
|
|
||||||
s.mux.Lock()
|
|
||||||
defer s.mux.Unlock()
|
|
||||||
|
|
||||||
accountId, accountIdFound := s.PeerKeyId2AccountId[peerKey]
|
|
||||||
if !accountIdFound {
|
|
||||||
return nil, status.Errorf(codes.NotFound, "peer not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
account, err := s.GetAccount(accountId)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if peer, ok := account.Peers[peerKey]; ok {
|
|
||||||
return peer, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, status.Errorf(codes.NotFound, "peer not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
// SaveAccount updates an existing account or adds a new one
|
|
||||||
func (s *FileStore) SaveAccount(account *Account) error {
|
func (s *FileStore) SaveAccount(account *Account) error {
|
||||||
s.mux.Lock()
|
s.mux.Lock()
|
||||||
defer s.mux.Unlock()
|
defer s.mux.Unlock()
|
||||||
|
|
||||||
|
accountCopy := account.Copy()
|
||||||
|
|
||||||
// todo will override, handle existing keys
|
// todo will override, handle existing keys
|
||||||
s.Accounts[account.Id] = account
|
s.Accounts[accountCopy.Id] = accountCopy
|
||||||
|
|
||||||
// todo check that account.Id and keyId are not exist already
|
// todo check that account.Id and keyId are not exist already
|
||||||
// because if keyId exists for other accounts this can be bad
|
// because if keyId exists for other accounts this can be bad
|
||||||
for keyId := range account.SetupKeys {
|
for keyID := range accountCopy.SetupKeys {
|
||||||
s.SetupKeyId2AccountId[strings.ToUpper(keyId)] = account.Id
|
s.SetupKeyID2AccountID[strings.ToUpper(keyID)] = accountCopy.Id
|
||||||
}
|
}
|
||||||
|
|
||||||
// enforce peer to account index and delete peer to route indexes for rebuild
|
// enforce peer to account index and delete peer to route indexes for rebuild
|
||||||
for _, peer := range account.Peers {
|
for _, peer := range accountCopy.Peers {
|
||||||
s.PeerKeyId2AccountId[peer.Key] = account.Id
|
s.PeerKeyID2AccountID[peer.Key] = accountCopy.Id
|
||||||
delete(s.PeerKeyID2RouteIDs, peer.Key)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
delete(s.AccountPrefix2RouteIDs, account.Id)
|
for _, user := range accountCopy.Users {
|
||||||
|
s.UserID2AccountID[user.Id] = accountCopy.Id
|
||||||
// remove all peers related to account from rules indexes
|
|
||||||
cleanIDs := make([]string, 0)
|
|
||||||
for key := range s.PeerKeyId2SrcRulesId {
|
|
||||||
if accountID, ok := s.PeerKeyId2AccountId[key]; ok && accountID == account.Id {
|
|
||||||
cleanIDs = append(cleanIDs, key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, key := range cleanIDs {
|
|
||||||
delete(s.PeerKeyId2SrcRulesId, key)
|
|
||||||
}
|
|
||||||
cleanIDs = cleanIDs[:0]
|
|
||||||
for key := range s.PeerKeyId2DstRulesId {
|
|
||||||
if accountID, ok := s.PeerKeyId2AccountId[key]; ok && accountID == account.Id {
|
|
||||||
cleanIDs = append(cleanIDs, key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, key := range cleanIDs {
|
|
||||||
delete(s.PeerKeyId2DstRulesId, key)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// rebuild rule indexes
|
if accountCopy.DomainCategory == PrivateCategory && accountCopy.IsDomainPrimaryAccount {
|
||||||
for _, rule := range account.Rules {
|
s.PrivateDomain2AccountID[accountCopy.Domain] = accountCopy.Id
|
||||||
for _, gid := range rule.Source {
|
|
||||||
g, ok := account.Groups[gid]
|
|
||||||
if !ok {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
for _, pid := range g.Peers {
|
|
||||||
rules := s.PeerKeyId2SrcRulesId[pid]
|
|
||||||
if rules == nil {
|
|
||||||
rules = map[string]struct{}{}
|
|
||||||
s.PeerKeyId2SrcRulesId[pid] = rules
|
|
||||||
}
|
|
||||||
rules[rule.ID] = struct{}{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, gid := range rule.Destination {
|
|
||||||
g, ok := account.Groups[gid]
|
|
||||||
if !ok {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
for _, pid := range g.Peers {
|
|
||||||
rules := s.PeerKeyId2DstRulesId[pid]
|
|
||||||
if rules == nil {
|
|
||||||
rules = map[string]struct{}{}
|
|
||||||
s.PeerKeyId2DstRulesId[pid] = rules
|
|
||||||
}
|
|
||||||
rules[rule.ID] = struct{}{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, route := range account.Routes {
|
|
||||||
if route.Peer == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if s.PeerKeyID2RouteIDs[route.Peer] == nil {
|
|
||||||
s.PeerKeyID2RouteIDs[route.Peer] = make(map[string]struct{})
|
|
||||||
}
|
|
||||||
s.PeerKeyID2RouteIDs[route.Peer][route.ID] = struct{}{}
|
|
||||||
if s.AccountPrefix2RouteIDs[account.Id] == nil {
|
|
||||||
s.AccountPrefix2RouteIDs[account.Id] = make(map[string][]string)
|
|
||||||
}
|
|
||||||
if _, ok := s.AccountPrefix2RouteIDs[account.Id][route.Network.String()]; !ok {
|
|
||||||
s.AccountPrefix2RouteIDs[account.Id][route.Network.String()] = make([]string, 0)
|
|
||||||
}
|
|
||||||
s.AccountPrefix2RouteIDs[account.Id][route.Network.String()] = append(
|
|
||||||
s.AccountPrefix2RouteIDs[account.Id][route.Network.String()],
|
|
||||||
route.ID,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, user := range account.Users {
|
|
||||||
s.UserId2AccountId[user.Id] = account.Id
|
|
||||||
}
|
|
||||||
|
|
||||||
if account.DomainCategory == PrivateCategory && account.IsDomainPrimaryAccount {
|
|
||||||
s.PrivateDomain2AccountId[account.Domain] = account.Id
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return s.persist(s.storeFile)
|
return s.persist(s.storeFile)
|
||||||
@@ -364,205 +185,152 @@ func (s *FileStore) SaveAccount(account *Account) error {
|
|||||||
|
|
||||||
// GetAccountByPrivateDomain returns account by private domain
|
// GetAccountByPrivateDomain returns account by private domain
|
||||||
func (s *FileStore) GetAccountByPrivateDomain(domain string) (*Account, error) {
|
func (s *FileStore) GetAccountByPrivateDomain(domain string) (*Account, error) {
|
||||||
accountId, accountIdFound := s.PrivateDomain2AccountId[strings.ToLower(domain)]
|
s.mux.Lock()
|
||||||
if !accountIdFound {
|
defer s.mux.Unlock()
|
||||||
return nil, status.Errorf(
|
|
||||||
codes.NotFound,
|
accountID, accountIDFound := s.PrivateDomain2AccountID[strings.ToLower(domain)]
|
||||||
"provided domain is not registered or is not private",
|
if !accountIDFound {
|
||||||
)
|
return nil, status.Errorf(status.NotFound, "account not found: provided domain is not registered or is not private")
|
||||||
}
|
}
|
||||||
|
|
||||||
account, err := s.GetAccount(accountId)
|
account, err := s.getAccount(accountID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return account, nil
|
return account.Copy(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAccountBySetupKey returns account by setup key id
|
// GetAccountBySetupKey returns account by setup key id
|
||||||
func (s *FileStore) GetAccountBySetupKey(setupKey string) (*Account, error) {
|
func (s *FileStore) GetAccountBySetupKey(setupKey string) (*Account, error) {
|
||||||
accountId, accountIdFound := s.SetupKeyId2AccountId[strings.ToUpper(setupKey)]
|
|
||||||
if !accountIdFound {
|
|
||||||
return nil, status.Errorf(codes.NotFound, "provided setup key doesn't exists")
|
|
||||||
}
|
|
||||||
|
|
||||||
account, err := s.GetAccount(accountId)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return account, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAccountPeers returns account peers
|
|
||||||
func (s *FileStore) GetAccountPeers(accountId string) ([]*Peer, error) {
|
|
||||||
s.mux.Lock()
|
s.mux.Lock()
|
||||||
defer s.mux.Unlock()
|
defer s.mux.Unlock()
|
||||||
|
|
||||||
account, err := s.GetAccount(accountId)
|
accountID, accountIDFound := s.SetupKeyID2AccountID[strings.ToUpper(setupKey)]
|
||||||
|
if !accountIDFound {
|
||||||
|
return nil, status.Errorf(status.NotFound, "account not found: provided setup key doesn't exists")
|
||||||
|
}
|
||||||
|
|
||||||
|
account, err := s.getAccount(accountID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var peers []*Peer
|
return account.Copy(), nil
|
||||||
for _, peer := range account.Peers {
|
|
||||||
peers = append(peers, peer)
|
|
||||||
}
|
|
||||||
|
|
||||||
return peers, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAllAccounts returns all accounts
|
// GetAllAccounts returns all accounts
|
||||||
func (s *FileStore) GetAllAccounts() (all []*Account) {
|
func (s *FileStore) GetAllAccounts() (all []*Account) {
|
||||||
|
s.mux.Lock()
|
||||||
|
defer s.mux.Unlock()
|
||||||
for _, a := range s.Accounts {
|
for _, a := range s.Accounts {
|
||||||
all = append(all, a)
|
all = append(all, a.Copy())
|
||||||
}
|
}
|
||||||
|
|
||||||
return all
|
return all
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAccount returns an account for id
|
// getAccount returns a reference to the Account. Should not return a copy.
|
||||||
func (s *FileStore) GetAccount(accountId string) (*Account, error) {
|
func (s *FileStore) getAccount(accountID string) (*Account, error) {
|
||||||
account, accountFound := s.Accounts[accountId]
|
account, accountFound := s.Accounts[accountID]
|
||||||
if !accountFound {
|
if !accountFound {
|
||||||
return nil, status.Errorf(codes.NotFound, "account not found")
|
return nil, status.Errorf(status.NotFound, "account not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
return account, nil
|
return account, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUserAccount returns a user account
|
// GetAccount returns an account for ID
|
||||||
func (s *FileStore) GetUserAccount(userId string) (*Account, error) {
|
func (s *FileStore) GetAccount(accountID string) (*Account, error) {
|
||||||
s.mux.Lock()
|
s.mux.Lock()
|
||||||
defer s.mux.Unlock()
|
defer s.mux.Unlock()
|
||||||
|
|
||||||
accountId, accountIdFound := s.UserId2AccountId[userId]
|
account, err := s.getAccount(accountID)
|
||||||
if !accountIdFound {
|
|
||||||
return nil, status.Errorf(codes.NotFound, "account not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.GetAccount(accountId)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *FileStore) getPeerAccount(peerKey string) (*Account, error) {
|
|
||||||
accountId, accountIdFound := s.PeerKeyId2AccountId[peerKey]
|
|
||||||
if !accountIdFound {
|
|
||||||
return nil, status.Errorf(codes.NotFound, "Provided peer key doesn't exists %s", peerKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.GetAccount(accountId)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPeerAccount returns user account if exists
|
|
||||||
func (s *FileStore) GetPeerAccount(peerKey string) (*Account, error) {
|
|
||||||
s.mux.Lock()
|
|
||||||
defer s.mux.Unlock()
|
|
||||||
|
|
||||||
return s.getPeerAccount(peerKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPeerSrcRules return list of source rules for peer
|
|
||||||
func (s *FileStore) GetPeerSrcRules(accountId, peerKey string) ([]*Rule, error) {
|
|
||||||
s.mux.Lock()
|
|
||||||
defer s.mux.Unlock()
|
|
||||||
|
|
||||||
account, err := s.GetAccount(accountId)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
ruleIDs, ok := s.PeerKeyId2SrcRulesId[peerKey]
|
return account.Copy(), nil
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("no rules for peer: %v", ruleIDs)
|
|
||||||
}
|
|
||||||
|
|
||||||
rules := []*Rule{}
|
|
||||||
for id := range ruleIDs {
|
|
||||||
rule, ok := account.Rules[id]
|
|
||||||
if ok {
|
|
||||||
rules = append(rules, rule)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return rules, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPeerDstRules return list of destination rules for peer
|
// GetAccountByUser returns a user account
|
||||||
func (s *FileStore) GetPeerDstRules(accountId, peerKey string) ([]*Rule, error) {
|
func (s *FileStore) GetAccountByUser(userID string) (*Account, error) {
|
||||||
s.mux.Lock()
|
s.mux.Lock()
|
||||||
defer s.mux.Unlock()
|
defer s.mux.Unlock()
|
||||||
|
|
||||||
account, err := s.GetAccount(accountId)
|
accountID, accountIDFound := s.UserID2AccountID[userID]
|
||||||
|
if !accountIDFound {
|
||||||
|
return nil, status.Errorf(status.NotFound, "account not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
account, err := s.getAccount(accountID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
ruleIDs, ok := s.PeerKeyId2DstRulesId[peerKey]
|
return account.Copy(), nil
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("no rules for peer: %v", ruleIDs)
|
|
||||||
}
|
|
||||||
|
|
||||||
rules := []*Rule{}
|
|
||||||
for id := range ruleIDs {
|
|
||||||
rule, ok := account.Rules[id]
|
|
||||||
if ok {
|
|
||||||
rules = append(rules, rule)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return rules, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPeerRoutes return list of routes for peer
|
// GetAccountByPeerPubKey returns an account for a given peer WireGuard public key
|
||||||
func (s *FileStore) GetPeerRoutes(peerKey string) ([]*route.Route, error) {
|
func (s *FileStore) GetAccountByPeerPubKey(peerKey string) (*Account, error) {
|
||||||
s.mux.Lock()
|
s.mux.Lock()
|
||||||
defer s.mux.Unlock()
|
defer s.mux.Unlock()
|
||||||
|
|
||||||
account, err := s.getPeerAccount(peerKey)
|
accountID, accountIDFound := s.PeerKeyID2AccountID[peerKey]
|
||||||
|
if !accountIDFound {
|
||||||
|
return nil, status.Errorf(status.NotFound, "provided peer key doesn't exists %s", peerKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
account, err := s.getAccount(accountID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var routes []*route.Route
|
return account.Copy(), nil
|
||||||
|
|
||||||
routeIDs, ok := s.PeerKeyID2RouteIDs[peerKey]
|
|
||||||
if !ok {
|
|
||||||
return routes, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
for id := range routeIDs {
|
|
||||||
route, found := account.Routes[id]
|
|
||||||
if found {
|
|
||||||
routes = append(routes, route)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return routes, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRoutesByPrefix return list of routes by account and route prefix
|
// GetInstallationID returns the installation ID from the store
|
||||||
func (s *FileStore) GetRoutesByPrefix(accountID string, prefix netip.Prefix) ([]*route.Route, error) {
|
func (s *FileStore) GetInstallationID() string {
|
||||||
|
return s.InstallationID
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveInstallationID saves the installation ID
|
||||||
|
func (s *FileStore) SaveInstallationID(ID string) error {
|
||||||
s.mux.Lock()
|
s.mux.Lock()
|
||||||
defer s.mux.Unlock()
|
defer s.mux.Unlock()
|
||||||
|
|
||||||
account, err := s.GetAccount(accountID)
|
s.InstallationID = ID
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
routeIDs, ok := s.AccountPrefix2RouteIDs[accountID][prefix.String()]
|
return s.persist(s.storeFile)
|
||||||
if !ok {
|
}
|
||||||
return nil, status.Errorf(codes.NotFound, "no routes for prefix: %v", prefix.String())
|
|
||||||
}
|
// SavePeerStatus stores the PeerStatus in memory. It doesn't attempt to persist data to speed up things.
|
||||||
|
// PeerStatus will be saved eventually when some other changes occur.
|
||||||
var routes []*route.Route
|
func (s *FileStore) SavePeerStatus(accountID, peerKey string, peerStatus PeerStatus) error {
|
||||||
for _, id := range routeIDs {
|
s.mux.Lock()
|
||||||
route, found := account.Routes[id]
|
defer s.mux.Unlock()
|
||||||
if found {
|
|
||||||
routes = append(routes, route)
|
account, err := s.getAccount(accountID)
|
||||||
}
|
if err != nil {
|
||||||
}
|
return err
|
||||||
|
}
|
||||||
return routes, nil
|
|
||||||
|
peer := account.Peers[peerKey]
|
||||||
|
if peer == nil {
|
||||||
|
return status.Errorf(status.NotFound, "peer %s not found", peerKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
peer.Status = &peerStatus
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close the FileStore persisting data to disk
|
||||||
|
func (s *FileStore) Close() error {
|
||||||
|
s.mux.Lock()
|
||||||
|
defer s.mux.Unlock()
|
||||||
|
|
||||||
|
log.Infof("closing FileStore")
|
||||||
|
|
||||||
|
return s.persist(s.storeFile)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package server
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/netbirdio/netbird/util"
|
"github.com/netbirdio/netbird/util"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"net"
|
"net"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -9,6 +10,10 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type accounts struct {
|
||||||
|
Accounts map[string]*Account
|
||||||
|
}
|
||||||
|
|
||||||
func TestNewStore(t *testing.T) {
|
func TestNewStore(t *testing.T) {
|
||||||
store := newStore(t)
|
store := newStore(t)
|
||||||
|
|
||||||
@@ -16,16 +21,16 @@ func TestNewStore(t *testing.T) {
|
|||||||
t.Errorf("expected to create a new empty Accounts map when creating a new FileStore")
|
t.Errorf("expected to create a new empty Accounts map when creating a new FileStore")
|
||||||
}
|
}
|
||||||
|
|
||||||
if store.SetupKeyId2AccountId == nil || len(store.SetupKeyId2AccountId) != 0 {
|
if store.SetupKeyID2AccountID == nil || len(store.SetupKeyID2AccountID) != 0 {
|
||||||
t.Errorf("expected to create a new empty SetupKeyId2AccountId map when creating a new FileStore")
|
t.Errorf("expected to create a new empty SetupKeyID2AccountID map when creating a new FileStore")
|
||||||
}
|
}
|
||||||
|
|
||||||
if store.PeerKeyId2AccountId == nil || len(store.PeerKeyId2AccountId) != 0 {
|
if store.PeerKeyID2AccountID == nil || len(store.PeerKeyID2AccountID) != 0 {
|
||||||
t.Errorf("expected to create a new empty PeerKeyId2AccountId map when creating a new FileStore")
|
t.Errorf("expected to create a new empty PeerKeyID2AccountID map when creating a new FileStore")
|
||||||
}
|
}
|
||||||
|
|
||||||
if store.UserId2AccountId == nil || len(store.UserId2AccountId) != 0 {
|
if store.UserID2AccountID == nil || len(store.UserID2AccountID) != 0 {
|
||||||
t.Errorf("expected to create a new empty UserId2AccountId map when creating a new FileStore")
|
t.Errorf("expected to create a new empty UserID2AccountID map when creating a new FileStore")
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -55,16 +60,16 @@ func TestSaveAccount(t *testing.T) {
|
|||||||
t.Errorf("expecting Account to be stored after SaveAccount()")
|
t.Errorf("expecting Account to be stored after SaveAccount()")
|
||||||
}
|
}
|
||||||
|
|
||||||
if store.PeerKeyId2AccountId["peerkey"] == "" {
|
if store.PeerKeyID2AccountID["peerkey"] == "" {
|
||||||
t.Errorf("expecting PeerKeyId2AccountId index updated after SaveAccount()")
|
t.Errorf("expecting PeerKeyID2AccountID index updated after SaveAccount()")
|
||||||
}
|
}
|
||||||
|
|
||||||
if store.UserId2AccountId["testuser"] == "" {
|
if store.UserID2AccountID["testuser"] == "" {
|
||||||
t.Errorf("expecting UserId2AccountId index updated after SaveAccount()")
|
t.Errorf("expecting UserID2AccountID index updated after SaveAccount()")
|
||||||
}
|
}
|
||||||
|
|
||||||
if store.SetupKeyId2AccountId[setupKey.Key] == "" {
|
if store.SetupKeyID2AccountID[setupKey.Key] == "" {
|
||||||
t.Errorf("expecting SetupKeyId2AccountId index updated after SaveAccount()")
|
t.Errorf("expecting SetupKeyID2AccountID index updated after SaveAccount()")
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -88,7 +93,7 @@ func TestStore(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
restored, err := NewStore(store.storeFile)
|
restored, err := NewFileStore(store.storeFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -124,7 +129,7 @@ func TestRestore(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
store, err := NewStore(storeDir)
|
store, err := NewFileStore(storeDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -141,11 +146,11 @@ func TestRestore(t *testing.T) {
|
|||||||
|
|
||||||
require.NotNil(t, account.SetupKeys["A2C8E62B-38F5-4553-B31E-DD66C696CEBB"], "failed to restore a FileStore file - missing Account SetupKey A2C8E62B-38F5-4553-B31E-DD66C696CEBB")
|
require.NotNil(t, account.SetupKeys["A2C8E62B-38F5-4553-B31E-DD66C696CEBB"], "failed to restore a FileStore file - missing Account SetupKey A2C8E62B-38F5-4553-B31E-DD66C696CEBB")
|
||||||
|
|
||||||
require.Len(t, store.UserId2AccountId, 2, "failed to restore a FileStore wrong UserId2AccountId mapping length")
|
require.Len(t, store.UserID2AccountID, 2, "failed to restore a FileStore wrong UserID2AccountID mapping length")
|
||||||
|
|
||||||
require.Len(t, store.SetupKeyId2AccountId, 1, "failed to restore a FileStore wrong SetupKeyId2AccountId mapping length")
|
require.Len(t, store.SetupKeyID2AccountID, 1, "failed to restore a FileStore wrong SetupKeyID2AccountID mapping length")
|
||||||
|
|
||||||
require.Len(t, store.PrivateDomain2AccountId, 1, "failed to restore a FileStore wrong PrivateDomain2AccountId mapping length")
|
require.Len(t, store.PrivateDomain2AccountID, 1, "failed to restore a FileStore wrong PrivateDomain2AccountID mapping length")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetAccountByPrivateDomain(t *testing.T) {
|
func TestGetAccountByPrivateDomain(t *testing.T) {
|
||||||
@@ -156,7 +161,7 @@ func TestGetAccountByPrivateDomain(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
store, err := NewStore(storeDir)
|
store, err := NewFileStore(storeDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -171,8 +176,101 @@ func TestGetAccountByPrivateDomain(t *testing.T) {
|
|||||||
require.Error(t, err, "should return error on domain lookup")
|
require.Error(t, err, "should return error on domain lookup")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFileStore_GetAccount(t *testing.T) {
|
||||||
|
storeDir := t.TempDir()
|
||||||
|
storeFile := filepath.Join(storeDir, "store.json")
|
||||||
|
err := util.CopyFileContents("testdata/store.json", storeFile)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
accounts := &accounts{}
|
||||||
|
_, err = util.ReadJson(storeFile, accounts)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
store, err := NewFileStore(storeDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := accounts.Accounts["bf1c8084-ba50-4ce7-9439-34653001fc3b"]
|
||||||
|
if expected == nil {
|
||||||
|
t.Fatalf("expected account doesn't exist")
|
||||||
|
}
|
||||||
|
|
||||||
|
account, err := store.GetAccount(expected.Id)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, expected.IsDomainPrimaryAccount, account.IsDomainPrimaryAccount)
|
||||||
|
assert.Equal(t, expected.DomainCategory, account.DomainCategory)
|
||||||
|
assert.Equal(t, expected.Domain, account.Domain)
|
||||||
|
assert.Equal(t, expected.CreatedBy, account.CreatedBy)
|
||||||
|
assert.Equal(t, expected.Network.Id, account.Network.Id)
|
||||||
|
assert.Len(t, account.Peers, len(expected.Peers))
|
||||||
|
assert.Len(t, account.Users, len(expected.Users))
|
||||||
|
assert.Len(t, account.SetupKeys, len(expected.SetupKeys))
|
||||||
|
assert.Len(t, account.Rules, len(expected.Rules))
|
||||||
|
assert.Len(t, account.Routes, len(expected.Routes))
|
||||||
|
assert.Len(t, account.NameServerGroups, len(expected.NameServerGroups))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileStore_SavePeerStatus(t *testing.T) {
|
||||||
|
storeDir := t.TempDir()
|
||||||
|
|
||||||
|
err := util.CopyFileContents("testdata/store.json", filepath.Join(storeDir, "store.json"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
store, err := NewFileStore(storeDir)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
account, err := store.getAccount("bf1c8084-ba50-4ce7-9439-34653001fc3b")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// save status of non-existing peer
|
||||||
|
newStatus := PeerStatus{Connected: true, LastSeen: time.Now()}
|
||||||
|
err = store.SavePeerStatus(account.Id, "non-existing-peer", newStatus)
|
||||||
|
assert.Error(t, err)
|
||||||
|
|
||||||
|
// save new status of existing peer
|
||||||
|
account.Peers["testpeer"] = &Peer{
|
||||||
|
Key: "peerkey",
|
||||||
|
SetupKey: "peerkeysetupkey",
|
||||||
|
IP: net.IP{127, 0, 0, 1},
|
||||||
|
Meta: PeerSystemMeta{},
|
||||||
|
Name: "peer name",
|
||||||
|
Status: &PeerStatus{Connected: false, LastSeen: time.Now()},
|
||||||
|
}
|
||||||
|
|
||||||
|
err = store.SaveAccount(account)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = store.SavePeerStatus(account.Id, "testpeer", newStatus)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
account, err = store.getAccount(account.Id)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := account.Peers["testpeer"].Status
|
||||||
|
assert.Equal(t, newStatus, *actual)
|
||||||
|
}
|
||||||
|
|
||||||
func newStore(t *testing.T) *FileStore {
|
func newStore(t *testing.T) *FileStore {
|
||||||
store, err := NewStore(t.TempDir())
|
store, err := NewFileStore(t.TempDir())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("failed creating a new store")
|
t.Errorf("failed creating a new store")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import "github.com/netbirdio/netbird/management/server/status"
|
||||||
"google.golang.org/grpc/codes"
|
|
||||||
"google.golang.org/grpc/status"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Group of the peers for ACL
|
// Group of the peers for ACL
|
||||||
type Group struct {
|
type Group struct {
|
||||||
@@ -47,12 +44,13 @@ func (g *Group) Copy() *Group {
|
|||||||
|
|
||||||
// GetGroup object of the peers
|
// GetGroup object of the peers
|
||||||
func (am *DefaultAccountManager) GetGroup(accountID, groupID string) (*Group, error) {
|
func (am *DefaultAccountManager) GetGroup(accountID, groupID string) (*Group, error) {
|
||||||
am.mux.Lock()
|
|
||||||
defer am.mux.Unlock()
|
unlock := am.Store.AcquireAccountLock(accountID)
|
||||||
|
defer unlock()
|
||||||
|
|
||||||
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, err
|
||||||
}
|
}
|
||||||
|
|
||||||
group, ok := account.Groups[groupID]
|
group, ok := account.Groups[groupID]
|
||||||
@@ -60,17 +58,18 @@ func (am *DefaultAccountManager) GetGroup(accountID, groupID string) (*Group, er
|
|||||||
return group, nil
|
return group, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, status.Errorf(codes.NotFound, "group with ID %s not found", groupID)
|
return nil, status.Errorf(status.NotFound, "group with ID %s not found", groupID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SaveGroup object of the peers
|
// SaveGroup object of the peers
|
||||||
func (am *DefaultAccountManager) SaveGroup(accountID string, group *Group) error {
|
func (am *DefaultAccountManager) SaveGroup(accountID string, group *Group) error {
|
||||||
am.mux.Lock()
|
|
||||||
defer am.mux.Unlock()
|
unlock := am.Store.AcquireAccountLock(accountID)
|
||||||
|
defer unlock()
|
||||||
|
|
||||||
account, err := am.Store.GetAccount(accountID)
|
account, err := am.Store.GetAccount(accountID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return status.Errorf(codes.NotFound, "account not found")
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
account.Groups[group.ID] = group
|
account.Groups[group.ID] = group
|
||||||
@@ -86,17 +85,18 @@ func (am *DefaultAccountManager) SaveGroup(accountID string, group *Group) error
|
|||||||
// UpdateGroup updates a group using a list of operations
|
// UpdateGroup updates a group using a list of operations
|
||||||
func (am *DefaultAccountManager) UpdateGroup(accountID string,
|
func (am *DefaultAccountManager) UpdateGroup(accountID string,
|
||||||
groupID string, operations []GroupUpdateOperation) (*Group, error) {
|
groupID string, operations []GroupUpdateOperation) (*Group, error) {
|
||||||
am.mux.Lock()
|
|
||||||
defer am.mux.Unlock()
|
unlock := am.Store.AcquireAccountLock(accountID)
|
||||||
|
defer unlock()
|
||||||
|
|
||||||
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, err
|
||||||
}
|
}
|
||||||
|
|
||||||
groupToUpdate, ok := account.Groups[groupID]
|
groupToUpdate, ok := account.Groups[groupID]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, status.Errorf(codes.NotFound, "group %s no longer exists", groupID)
|
return nil, status.Errorf(status.NotFound, "group with ID %s no longer exists", groupID)
|
||||||
}
|
}
|
||||||
|
|
||||||
group := groupToUpdate.Copy()
|
group := groupToUpdate.Copy()
|
||||||
@@ -127,7 +127,7 @@ func (am *DefaultAccountManager) UpdateGroup(accountID string,
|
|||||||
|
|
||||||
err = am.updateAccountPeers(account)
|
err = am.updateAccountPeers(account)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.Errorf(codes.Internal, "failed to update account peers")
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return group, nil
|
return group, nil
|
||||||
@@ -135,12 +135,13 @@ func (am *DefaultAccountManager) UpdateGroup(accountID string,
|
|||||||
|
|
||||||
// DeleteGroup object of the peers
|
// DeleteGroup object of the peers
|
||||||
func (am *DefaultAccountManager) DeleteGroup(accountID, groupID string) error {
|
func (am *DefaultAccountManager) DeleteGroup(accountID, groupID string) error {
|
||||||
am.mux.Lock()
|
|
||||||
defer am.mux.Unlock()
|
unlock := am.Store.AcquireAccountLock(accountID)
|
||||||
|
defer unlock()
|
||||||
|
|
||||||
account, err := am.Store.GetAccount(accountID)
|
account, err := am.Store.GetAccount(accountID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return status.Errorf(codes.NotFound, "account not found")
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
delete(account.Groups, groupID)
|
delete(account.Groups, groupID)
|
||||||
@@ -155,12 +156,13 @@ func (am *DefaultAccountManager) DeleteGroup(accountID, groupID string) error {
|
|||||||
|
|
||||||
// ListGroups objects of the peers
|
// ListGroups objects of the peers
|
||||||
func (am *DefaultAccountManager) ListGroups(accountID string) ([]*Group, error) {
|
func (am *DefaultAccountManager) ListGroups(accountID string) ([]*Group, error) {
|
||||||
am.mux.Lock()
|
|
||||||
defer am.mux.Unlock()
|
unlock := am.Store.AcquireAccountLock(accountID)
|
||||||
|
defer unlock()
|
||||||
|
|
||||||
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, err
|
||||||
}
|
}
|
||||||
|
|
||||||
groups := make([]*Group, 0, len(account.Groups))
|
groups := make([]*Group, 0, len(account.Groups))
|
||||||
@@ -173,17 +175,18 @@ func (am *DefaultAccountManager) ListGroups(accountID string) ([]*Group, error)
|
|||||||
|
|
||||||
// GroupAddPeer appends peer to the group
|
// GroupAddPeer appends peer to the group
|
||||||
func (am *DefaultAccountManager) GroupAddPeer(accountID, groupID, peerKey string) error {
|
func (am *DefaultAccountManager) GroupAddPeer(accountID, groupID, peerKey string) error {
|
||||||
am.mux.Lock()
|
|
||||||
defer am.mux.Unlock()
|
unlock := am.Store.AcquireAccountLock(accountID)
|
||||||
|
defer unlock()
|
||||||
|
|
||||||
account, err := am.Store.GetAccount(accountID)
|
account, err := am.Store.GetAccount(accountID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return status.Errorf(codes.NotFound, "account not found")
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
group, ok := account.Groups[groupID]
|
group, ok := account.Groups[groupID]
|
||||||
if !ok {
|
if !ok {
|
||||||
return status.Errorf(codes.NotFound, "group with ID %s not found", groupID)
|
return status.Errorf(status.NotFound, "group with ID %s not found", groupID)
|
||||||
}
|
}
|
||||||
|
|
||||||
add := true
|
add := true
|
||||||
@@ -207,17 +210,18 @@ func (am *DefaultAccountManager) GroupAddPeer(accountID, groupID, peerKey string
|
|||||||
|
|
||||||
// GroupDeletePeer removes peer from the group
|
// GroupDeletePeer removes peer from the group
|
||||||
func (am *DefaultAccountManager) GroupDeletePeer(accountID, groupID, peerKey string) error {
|
func (am *DefaultAccountManager) GroupDeletePeer(accountID, groupID, peerKey string) error {
|
||||||
am.mux.Lock()
|
|
||||||
defer am.mux.Unlock()
|
unlock := am.Store.AcquireAccountLock(accountID)
|
||||||
|
defer unlock()
|
||||||
|
|
||||||
account, err := am.Store.GetAccount(accountID)
|
account, err := am.Store.GetAccount(accountID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return status.Errorf(codes.NotFound, "account not found")
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
group, ok := account.Groups[groupID]
|
group, ok := account.Groups[groupID]
|
||||||
if !ok {
|
if !ok {
|
||||||
return status.Errorf(codes.NotFound, "group with ID %s not found", groupID)
|
return status.Errorf(status.NotFound, "group with ID %s not found", groupID)
|
||||||
}
|
}
|
||||||
|
|
||||||
account.Network.IncSerial()
|
account.Network.IncSerial()
|
||||||
@@ -225,7 +229,7 @@ func (am *DefaultAccountManager) GroupDeletePeer(accountID, groupID, peerKey str
|
|||||||
if itemID == peerKey {
|
if itemID == peerKey {
|
||||||
group.Peers = append(group.Peers[:i], group.Peers[i+1:]...)
|
group.Peers = append(group.Peers[:i], group.Peers[i+1:]...)
|
||||||
if err := am.Store.SaveAccount(account); err != nil {
|
if err := am.Store.SaveAccount(account); err != nil {
|
||||||
return status.Errorf(codes.Internal, "can't save account")
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -235,17 +239,18 @@ func (am *DefaultAccountManager) GroupDeletePeer(accountID, groupID, peerKey str
|
|||||||
|
|
||||||
// GroupListPeers returns list of the peers from the group
|
// GroupListPeers returns list of the peers from the group
|
||||||
func (am *DefaultAccountManager) GroupListPeers(accountID, groupID string) ([]*Peer, error) {
|
func (am *DefaultAccountManager) GroupListPeers(accountID, groupID string) ([]*Peer, error) {
|
||||||
am.mux.Lock()
|
|
||||||
defer am.mux.Unlock()
|
unlock := am.Store.AcquireAccountLock(accountID)
|
||||||
|
defer unlock()
|
||||||
|
|
||||||
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(status.NotFound, "account not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
group, ok := account.Groups[groupID]
|
group, ok := account.Groups[groupID]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, status.Errorf(codes.NotFound, "group with ID %s not found", groupID)
|
return nil, status.Errorf(status.NotFound, "group with ID %s not found", groupID)
|
||||||
}
|
}
|
||||||
|
|
||||||
peers := make([]*Peer, 0, len(account.Groups))
|
peers := make([]*Peer, 0, len(account.Groups))
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package server
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/netbirdio/netbird/route"
|
"github.com/netbirdio/netbird/management/server/telemetry"
|
||||||
gPeer "google.golang.org/grpc/peer"
|
gPeer "google.golang.org/grpc/peer"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -14,9 +14,11 @@ import (
|
|||||||
"github.com/golang/protobuf/ptypes/timestamp"
|
"github.com/golang/protobuf/ptypes/timestamp"
|
||||||
"github.com/netbirdio/netbird/encryption"
|
"github.com/netbirdio/netbird/encryption"
|
||||||
"github.com/netbirdio/netbird/management/proto"
|
"github.com/netbirdio/netbird/management/proto"
|
||||||
|
internalStatus "github.com/netbirdio/netbird/management/server/status"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc/codes"
|
||||||
|
gRPCPeer "google.golang.org/grpc/peer"
|
||||||
"google.golang.org/grpc/status"
|
"google.golang.org/grpc/status"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -29,10 +31,12 @@ type GRPCServer struct {
|
|||||||
config *Config
|
config *Config
|
||||||
turnCredentialsManager TURNCredentialsManager
|
turnCredentialsManager TURNCredentialsManager
|
||||||
jwtMiddleware *middleware.JWTMiddleware
|
jwtMiddleware *middleware.JWTMiddleware
|
||||||
|
appMetrics telemetry.AppMetrics
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewServer creates a new Management server
|
// NewServer creates a new Management server
|
||||||
func NewServer(config *Config, accountManager AccountManager, peersUpdateManager *PeersUpdateManager, turnCredentialsManager TURNCredentialsManager) (*GRPCServer, error) {
|
func NewServer(config *Config, accountManager AccountManager, peersUpdateManager *PeersUpdateManager,
|
||||||
|
turnCredentialsManager TURNCredentialsManager, appMetrics telemetry.AppMetrics) (*GRPCServer, error) {
|
||||||
key, err := wgtypes.GeneratePrivateKey()
|
key, err := wgtypes.GeneratePrivateKey()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -52,6 +56,16 @@ func NewServer(config *Config, accountManager AccountManager, peersUpdateManager
|
|||||||
log.Debug("unable to use http config to create new jwt middleware")
|
log.Debug("unable to use http config to create new jwt middleware")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if appMetrics != nil {
|
||||||
|
// update gauge based on number of connected peers which is equal to open gRPC streams
|
||||||
|
err = appMetrics.GRPCMetrics().RegisterConnectedStreams(func() int64 {
|
||||||
|
return int64(len(peersUpdateManager.peerChannels))
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return &GRPCServer{
|
return &GRPCServer{
|
||||||
wgKey: key,
|
wgKey: key,
|
||||||
// peerKey -> event channel
|
// peerKey -> event channel
|
||||||
@@ -60,11 +74,15 @@ func NewServer(config *Config, accountManager AccountManager, peersUpdateManager
|
|||||||
config: config,
|
config: config,
|
||||||
turnCredentialsManager: turnCredentialsManager,
|
turnCredentialsManager: turnCredentialsManager,
|
||||||
jwtMiddleware: jwtMiddleware,
|
jwtMiddleware: jwtMiddleware,
|
||||||
|
appMetrics: appMetrics,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *GRPCServer) GetServerKey(ctx context.Context, req *proto.Empty) (*proto.ServerKeyResponse, error) {
|
func (s *GRPCServer) GetServerKey(ctx context.Context, req *proto.Empty) (*proto.ServerKeyResponse, error) {
|
||||||
// todo introduce something more meaningful with the key expiration/rotation
|
// todo introduce something more meaningful with the key expiration/rotation
|
||||||
|
if s.appMetrics != nil {
|
||||||
|
s.appMetrics.GRPCMetrics().CountGetKeyRequest()
|
||||||
|
}
|
||||||
now := time.Now().Add(24 * time.Hour)
|
now := time.Now().Add(24 * time.Hour)
|
||||||
secs := int64(now.Second())
|
secs := int64(now.Second())
|
||||||
nanos := int32(now.Nanosecond())
|
nanos := int32(now.Nanosecond())
|
||||||
@@ -79,7 +97,13 @@ func (s *GRPCServer) GetServerKey(ctx context.Context, req *proto.Empty) (*proto
|
|||||||
// Sync validates the existence of a connecting peer, sends an initial state (all available for the connecting peers) and
|
// Sync validates the existence of a connecting peer, sends an initial state (all available for the connecting peers) and
|
||||||
// notifies the connected peer of any updates (e.g. new peers under the same account)
|
// notifies the connected peer of any updates (e.g. new peers under the same account)
|
||||||
func (s *GRPCServer) Sync(req *proto.EncryptedMessage, srv proto.ManagementService_SyncServer) error {
|
func (s *GRPCServer) Sync(req *proto.EncryptedMessage, srv proto.ManagementService_SyncServer) error {
|
||||||
log.Debugf("Sync request from peer %s", req.WgPubKey)
|
if s.appMetrics != nil {
|
||||||
|
s.appMetrics.GRPCMetrics().CountSyncRequest()
|
||||||
|
}
|
||||||
|
p, ok := gRPCPeer.FromContext(srv.Context())
|
||||||
|
if ok {
|
||||||
|
log.Debugf("Sync request from peer [%s] [%s]", req.WgPubKey, p.Addr.String())
|
||||||
|
}
|
||||||
|
|
||||||
peerKey, err := wgtypes.ParseKey(req.GetWgPubKey())
|
peerKey, err := wgtypes.ParseKey(req.GetWgPubKey())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -162,7 +186,7 @@ func (s *GRPCServer) Sync(req *proto.EncryptedMessage, srv proto.ManagementServi
|
|||||||
func (s *GRPCServer) registerPeer(peerKey wgtypes.Key, req *proto.LoginRequest) (*Peer, error) {
|
func (s *GRPCServer) registerPeer(peerKey wgtypes.Key, req *proto.LoginRequest) (*Peer, error) {
|
||||||
var (
|
var (
|
||||||
reqSetupKey string
|
reqSetupKey string
|
||||||
userId string
|
userID string
|
||||||
)
|
)
|
||||||
|
|
||||||
if req.GetJwtToken() != "" {
|
if req.GetJwtToken() != "" {
|
||||||
@@ -177,16 +201,16 @@ func (s *GRPCServer) registerPeer(peerKey wgtypes.Key, req *proto.LoginRequest)
|
|||||||
return nil, status.Errorf(codes.Internal, "invalid jwt token, err: %v", err)
|
return nil, status.Errorf(codes.Internal, "invalid jwt token, err: %v", err)
|
||||||
}
|
}
|
||||||
claims := jwtclaims.ExtractClaimsWithToken(token, s.config.HttpConfig.AuthAudience)
|
claims := jwtclaims.ExtractClaimsWithToken(token, s.config.HttpConfig.AuthAudience)
|
||||||
_, err = s.accountManager.GetAccountWithAuthorizationClaims(claims)
|
userID = claims.UserId
|
||||||
|
// we need to call this method because if user is new, we will automatically add it to existing or create a new account
|
||||||
|
_, _, err = s.accountManager.GetAccountFromToken(claims)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.Errorf(codes.Internal, "unable to fetch account with claims, err: %v", err)
|
return nil, status.Errorf(codes.Internal, "unable to fetch account with claims, err: %v", err)
|
||||||
}
|
}
|
||||||
userId = claims.UserId
|
|
||||||
} else {
|
} else {
|
||||||
log.Debugln("using setup key to register peer")
|
log.Debugln("using setup key to register peer")
|
||||||
|
|
||||||
reqSetupKey = req.GetSetupKey()
|
reqSetupKey = req.GetSetupKey()
|
||||||
userId = ""
|
userID = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
meta := req.GetMeta()
|
meta := req.GetMeta()
|
||||||
@@ -199,7 +223,7 @@ func (s *GRPCServer) registerPeer(peerKey wgtypes.Key, req *proto.LoginRequest)
|
|||||||
sshKey = req.GetPeerKeys().GetSshPubKey()
|
sshKey = req.GetPeerKeys().GetSshPubKey()
|
||||||
}
|
}
|
||||||
|
|
||||||
peer, err := s.accountManager.AddPeer(reqSetupKey, userId, &Peer{
|
peer, err := s.accountManager.AddPeer(reqSetupKey, userID, &Peer{
|
||||||
Key: peerKey.String(),
|
Key: peerKey.String(),
|
||||||
Name: meta.GetHostname(),
|
Name: meta.GetHostname(),
|
||||||
SSHKey: string(sshKey),
|
SSHKey: string(sshKey),
|
||||||
@@ -215,13 +239,16 @@ func (s *GRPCServer) registerPeer(peerKey wgtypes.Key, req *proto.LoginRequest)
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s, ok := status.FromError(err)
|
if e, ok := internalStatus.FromError(err); ok {
|
||||||
if ok {
|
switch e.Type() {
|
||||||
if s.Code() == codes.FailedPrecondition || s.Code() == codes.OutOfRange {
|
case internalStatus.PreconditionFailed:
|
||||||
return nil, err
|
return nil, status.Errorf(codes.FailedPrecondition, e.Message)
|
||||||
|
case internalStatus.NotFound:
|
||||||
|
return nil, status.Errorf(codes.NotFound, e.Message)
|
||||||
|
default:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil, status.Errorf(codes.NotFound, "provided setup key doesn't exists")
|
return nil, status.Errorf(codes.Internal, "failed registering new peer")
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo move to DefaultAccountManager the code below
|
// todo move to DefaultAccountManager the code below
|
||||||
@@ -229,17 +256,14 @@ func (s *GRPCServer) registerPeer(peerKey wgtypes.Key, req *proto.LoginRequest)
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.Errorf(codes.Internal, "unable to fetch network map after registering peer, error: %v", err)
|
return nil, status.Errorf(codes.Internal, "unable to fetch network map after registering peer, error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// notify other peers of our registration
|
// notify other peers of our registration
|
||||||
for _, remotePeer := range networkMap.Peers {
|
for _, remotePeer := range networkMap.Peers {
|
||||||
// exclude notified peer and add ourselves
|
remotePeerNetworkMap, err := s.accountManager.GetNetworkMap(remotePeer.Key)
|
||||||
peersToSend := []*Peer{peer}
|
if err != nil {
|
||||||
for _, p := range networkMap.Peers {
|
return nil, status.Errorf(codes.Internal, "unable to fetch network map after registering peer, error: %v", err)
|
||||||
if remotePeer.Key != p.Key {
|
|
||||||
peersToSend = append(peersToSend, p)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
update := toSyncResponse(s.config, remotePeer, peersToSend, networkMap.Routes, nil, networkMap.Network.CurrentSerial(), networkMap.Network)
|
|
||||||
|
update := toSyncResponse(s.config, remotePeer, nil, remotePeerNetworkMap)
|
||||||
err = s.peersUpdateManager.SendUpdate(remotePeer.Key, &UpdateMessage{Update: update})
|
err = s.peersUpdateManager.SendUpdate(remotePeer.Key, &UpdateMessage{Update: update})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// todo rethink if we should keep this return
|
// todo rethink if we should keep this return
|
||||||
@@ -255,7 +279,13 @@ func (s *GRPCServer) registerPeer(peerKey wgtypes.Key, req *proto.LoginRequest)
|
|||||||
// In case it isn't, the endpoint checks whether setup key is provided within the request and tries to register a peer.
|
// In case it isn't, the endpoint checks whether setup key is provided within the request and tries to register a peer.
|
||||||
// In case of the successful registration login is also successful
|
// In case of the successful registration login is also successful
|
||||||
func (s *GRPCServer) Login(ctx context.Context, req *proto.EncryptedMessage) (*proto.EncryptedMessage, error) {
|
func (s *GRPCServer) Login(ctx context.Context, req *proto.EncryptedMessage) (*proto.EncryptedMessage, error) {
|
||||||
log.Debugf("Login request from peer %s", req.WgPubKey)
|
if s.appMetrics != nil {
|
||||||
|
s.appMetrics.GRPCMetrics().CountLoginRequest()
|
||||||
|
}
|
||||||
|
p, ok := gRPCPeer.FromContext(ctx)
|
||||||
|
if ok {
|
||||||
|
log.Debugf("Login request from peer [%s] [%s]", req.WgPubKey, p.Addr.String())
|
||||||
|
}
|
||||||
|
|
||||||
peerKey, err := wgtypes.ParseKey(req.GetWgPubKey())
|
peerKey, err := wgtypes.ParseKey(req.GetWgPubKey())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -271,7 +301,7 @@ func (s *GRPCServer) Login(ctx context.Context, req *proto.EncryptedMessage) (*p
|
|||||||
|
|
||||||
peer, err := s.accountManager.GetPeer(peerKey.String())
|
peer, err := s.accountManager.GetPeer(peerKey.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errStatus, ok := status.FromError(err); ok && errStatus.Code() == codes.NotFound {
|
if errStatus, ok := internalStatus.FromError(err); ok && errStatus.Type() == internalStatus.NotFound {
|
||||||
// peer doesn't exist -> check if setup key was provided
|
// peer doesn't exist -> check if setup key was provided
|
||||||
if loginReq.GetJwtToken() == "" && loginReq.GetSetupKey() == "" {
|
if loginReq.GetJwtToken() == "" && loginReq.GetSetupKey() == "" {
|
||||||
// absent setup key or jwt -> permission denied
|
// absent setup key or jwt -> permission denied
|
||||||
@@ -357,12 +387,14 @@ func ToResponseProto(configProto Protocol) proto.HostConfig_Protocol {
|
|||||||
case TCP:
|
case TCP:
|
||||||
return proto.HostConfig_TCP
|
return proto.HostConfig_TCP
|
||||||
default:
|
default:
|
||||||
// mbragin: todo something better?
|
|
||||||
panic(fmt.Errorf("unexpected config protocol type %v", configProto))
|
panic(fmt.Errorf("unexpected config protocol type %v", configProto))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func toWiretrusteeConfig(config *Config, turnCredentials *TURNCredentials) *proto.WiretrusteeConfig {
|
func toWiretrusteeConfig(config *Config, turnCredentials *TURNCredentials) *proto.WiretrusteeConfig {
|
||||||
|
if config == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
var stuns []*proto.HostConfig
|
var stuns []*proto.HostConfig
|
||||||
for _, stun := range config.Stuns {
|
for _, stun := range config.Stuns {
|
||||||
stuns = append(stuns, &proto.HostConfig{
|
stuns = append(stuns, &proto.HostConfig{
|
||||||
@@ -421,14 +453,16 @@ func toRemotePeerConfig(peers []*Peer) []*proto.RemotePeerConfig {
|
|||||||
return remotePeers
|
return remotePeers
|
||||||
}
|
}
|
||||||
|
|
||||||
func toSyncResponse(config *Config, peer *Peer, peers []*Peer, routes []*route.Route, turnCredentials *TURNCredentials, serial uint64, network *Network) *proto.SyncResponse {
|
func toSyncResponse(config *Config, peer *Peer, turnCredentials *TURNCredentials, networkMap *NetworkMap) *proto.SyncResponse {
|
||||||
wtConfig := toWiretrusteeConfig(config, turnCredentials)
|
wtConfig := toWiretrusteeConfig(config, turnCredentials)
|
||||||
|
|
||||||
pConfig := toPeerConfig(peer, network)
|
pConfig := toPeerConfig(peer, networkMap.Network)
|
||||||
|
|
||||||
remotePeers := toRemotePeerConfig(peers)
|
remotePeers := toRemotePeerConfig(networkMap.Peers)
|
||||||
|
|
||||||
routesUpdate := toProtocolRoutes(routes)
|
routesUpdate := toProtocolRoutes(networkMap.Routes)
|
||||||
|
|
||||||
|
dnsUpdate := toProtocolDNSConfig(networkMap.DNSConfig)
|
||||||
|
|
||||||
return &proto.SyncResponse{
|
return &proto.SyncResponse{
|
||||||
WiretrusteeConfig: wtConfig,
|
WiretrusteeConfig: wtConfig,
|
||||||
@@ -436,11 +470,12 @@ func toSyncResponse(config *Config, peer *Peer, peers []*Peer, routes []*route.R
|
|||||||
RemotePeers: remotePeers,
|
RemotePeers: remotePeers,
|
||||||
RemotePeersIsEmpty: len(remotePeers) == 0,
|
RemotePeersIsEmpty: len(remotePeers) == 0,
|
||||||
NetworkMap: &proto.NetworkMap{
|
NetworkMap: &proto.NetworkMap{
|
||||||
Serial: serial,
|
Serial: networkMap.Network.CurrentSerial(),
|
||||||
PeerConfig: pConfig,
|
PeerConfig: pConfig,
|
||||||
RemotePeers: remotePeers,
|
RemotePeers: remotePeers,
|
||||||
RemotePeersIsEmpty: len(remotePeers) == 0,
|
RemotePeersIsEmpty: len(remotePeers) == 0,
|
||||||
Routes: routesUpdate,
|
Routes: routesUpdate,
|
||||||
|
DNSConfig: dnsUpdate,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -466,7 +501,7 @@ func (s *GRPCServer) sendInitialSync(peerKey wgtypes.Key, peer *Peer, srv proto.
|
|||||||
} else {
|
} else {
|
||||||
turnCredentials = nil
|
turnCredentials = nil
|
||||||
}
|
}
|
||||||
plainResp := toSyncResponse(s.config, peer, networkMap.Peers, networkMap.Routes, turnCredentials, networkMap.Network.CurrentSerial(), networkMap.Network)
|
plainResp := toSyncResponse(s.config, peer, turnCredentials, networkMap)
|
||||||
|
|
||||||
encryptedResp, err := encryption.EncryptMessage(peerKey, s.wgKey, plainResp)
|
encryptedResp, err := encryption.EncryptMessage(peerKey, s.wgKey, plainResp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -3,3 +3,5 @@ generate:
|
|||||||
models: true
|
models: true
|
||||||
embedded-spec: false
|
embedded-spec: false
|
||||||
output: types.gen.go
|
output: types.gen.go
|
||||||
|
compatibility:
|
||||||
|
always-prefix-enum-values: true
|
||||||
@@ -11,6 +11,6 @@ fi
|
|||||||
old_pwd=$(pwd)
|
old_pwd=$(pwd)
|
||||||
script_path=$(dirname $(realpath "$0"))
|
script_path=$(dirname $(realpath "$0"))
|
||||||
cd "$script_path"
|
cd "$script_path"
|
||||||
go install github.com/deepmap/oapi-codegen/cmd/oapi-codegen@v1.11.0
|
go install github.com/deepmap/oapi-codegen/cmd/oapi-codegen@4a1477f6a8ba6ca8115cc23bb2fb67f0b9fca18e
|
||||||
oapi-codegen --config cfg.yaml openapi.yml
|
oapi-codegen --config cfg.yaml openapi.yml
|
||||||
cd "$old_pwd"
|
cd "$old_pwd"
|
||||||
@@ -16,6 +16,8 @@ tags:
|
|||||||
description: Interact with and view information about rules.
|
description: Interact with and view information about rules.
|
||||||
- name: Routes
|
- name: Routes
|
||||||
description: Interact with and view information about routes.
|
description: Interact with and view information about routes.
|
||||||
|
- name: DNS
|
||||||
|
description: Interact with and view information about DNS configuration.
|
||||||
components:
|
components:
|
||||||
schemas:
|
schemas:
|
||||||
User:
|
User:
|
||||||
@@ -31,13 +33,59 @@ components:
|
|||||||
description: User's name from idp provider
|
description: User's name from idp provider
|
||||||
type: string
|
type: string
|
||||||
role:
|
role:
|
||||||
description: User's Netbird account role
|
description: User's NetBird account role
|
||||||
type: string
|
type: string
|
||||||
|
status:
|
||||||
|
description: User's status
|
||||||
|
type: string
|
||||||
|
enum: [ "active","invited","disabled" ]
|
||||||
|
auto_groups:
|
||||||
|
description: Groups to auto-assign to peers registered by this user
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
required:
|
required:
|
||||||
- id
|
- id
|
||||||
- email
|
- email
|
||||||
- name
|
- name
|
||||||
- role
|
- role
|
||||||
|
- auto_groups
|
||||||
|
- status
|
||||||
|
UserRequest:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
role:
|
||||||
|
description: User's NetBird account role
|
||||||
|
type: string
|
||||||
|
auto_groups:
|
||||||
|
description: Groups to auto-assign to peers registered by this user
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- role
|
||||||
|
- auto_groups
|
||||||
|
UserCreateRequest:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
role:
|
||||||
|
description: User's NetBird account role
|
||||||
|
type: string
|
||||||
|
email:
|
||||||
|
description: User's Email to send invite to
|
||||||
|
type: string
|
||||||
|
name:
|
||||||
|
description: User's full name
|
||||||
|
type: string
|
||||||
|
auto_groups:
|
||||||
|
description: Groups to auto-assign to peers registered by this user
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- role
|
||||||
|
- auto_groups
|
||||||
|
- email
|
||||||
PeerMinimum:
|
PeerMinimum:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
@@ -76,20 +124,21 @@ components:
|
|||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
$ref: '#/components/schemas/GroupMinimum'
|
$ref: '#/components/schemas/GroupMinimum'
|
||||||
activated_by:
|
|
||||||
description: Provides information of who activated the Peer. User or Setup Key
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
type:
|
|
||||||
type: string
|
|
||||||
value:
|
|
||||||
type: string
|
|
||||||
required:
|
|
||||||
- type
|
|
||||||
- value
|
|
||||||
ssh_enabled:
|
ssh_enabled:
|
||||||
description: Indicates whether SSH server is enabled on this peer
|
description: Indicates whether SSH server is enabled on this peer
|
||||||
type: boolean
|
type: boolean
|
||||||
|
user_id:
|
||||||
|
description: User ID of the user that enrolled this peer
|
||||||
|
type: string
|
||||||
|
hostname:
|
||||||
|
description: Hostname of the machine
|
||||||
|
type: string
|
||||||
|
ui_version:
|
||||||
|
description: Peer's desktop UI version
|
||||||
|
type: string
|
||||||
|
dns_label:
|
||||||
|
description: Peer's DNS label is the parsed peer name for domain resolution. It is used to form an FQDN by appending the account's domain to the peer label. e.g. peer-dns-label.netbird.cloud
|
||||||
|
type: string
|
||||||
required:
|
required:
|
||||||
- ip
|
- ip
|
||||||
- connected
|
- connected
|
||||||
@@ -97,8 +146,9 @@ components:
|
|||||||
- os
|
- os
|
||||||
- version
|
- version
|
||||||
- groups
|
- groups
|
||||||
- activated_by
|
|
||||||
- ssh_enabled
|
- ssh_enabled
|
||||||
|
- hostname
|
||||||
|
- dns_label
|
||||||
SetupKey:
|
SetupKey:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
@@ -134,6 +184,15 @@ components:
|
|||||||
state:
|
state:
|
||||||
description: Setup key status, "valid", "overused","expired" or "revoked"
|
description: Setup key status, "valid", "overused","expired" or "revoked"
|
||||||
type: string
|
type: string
|
||||||
|
auto_groups:
|
||||||
|
description: Setup key groups to auto-assign to peers registered with this key
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
updated_at:
|
||||||
|
description: Setup key last update date
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
required:
|
required:
|
||||||
- id
|
- id
|
||||||
- key
|
- key
|
||||||
@@ -145,6 +204,8 @@ components:
|
|||||||
- used_times
|
- used_times
|
||||||
- last_used
|
- last_used
|
||||||
- state
|
- state
|
||||||
|
- auto_groups
|
||||||
|
- updated_at
|
||||||
SetupKeyRequest:
|
SetupKeyRequest:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
@@ -160,11 +221,17 @@ components:
|
|||||||
revoked:
|
revoked:
|
||||||
description: Setup key revocation status
|
description: Setup key revocation status
|
||||||
type: boolean
|
type: boolean
|
||||||
|
auto_groups:
|
||||||
|
description: Setup key groups to auto-assign to peers registered with this key
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
required:
|
required:
|
||||||
- name
|
- name
|
||||||
- type
|
- type
|
||||||
- expires_in
|
- expires_in
|
||||||
- revoked
|
- revoked
|
||||||
|
- auto_groups
|
||||||
GroupMinimum:
|
GroupMinimum:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
@@ -338,6 +405,88 @@ components:
|
|||||||
enum: [ "network","network_id","description","enabled","peer","metric","masquerade" ]
|
enum: [ "network","network_id","description","enabled","peer","metric","masquerade" ]
|
||||||
required:
|
required:
|
||||||
- path
|
- path
|
||||||
|
Nameserver:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
ip:
|
||||||
|
description: Nameserver IP
|
||||||
|
type: string
|
||||||
|
ns_type:
|
||||||
|
description: Nameserver Type
|
||||||
|
type: string
|
||||||
|
enum: ["udp"]
|
||||||
|
port:
|
||||||
|
description: Nameserver Port
|
||||||
|
type: integer
|
||||||
|
required:
|
||||||
|
- ip
|
||||||
|
- ns_type
|
||||||
|
- port
|
||||||
|
NameserverGroupRequest:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
description: Nameserver group name
|
||||||
|
type: string
|
||||||
|
maxLength: 40
|
||||||
|
minLength: 1
|
||||||
|
description:
|
||||||
|
description: Nameserver group description
|
||||||
|
type: string
|
||||||
|
nameservers:
|
||||||
|
description: Nameserver group
|
||||||
|
minLength: 1
|
||||||
|
maxLength: 2
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/Nameserver'
|
||||||
|
enabled:
|
||||||
|
description: Nameserver group status
|
||||||
|
type: boolean
|
||||||
|
groups:
|
||||||
|
description: Nameserver group tag groups
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
primary:
|
||||||
|
description: Nameserver group primary status
|
||||||
|
type: boolean
|
||||||
|
domains:
|
||||||
|
description: Nameserver group domain list
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
|
maxLength: 255
|
||||||
|
required:
|
||||||
|
- name
|
||||||
|
- description
|
||||||
|
- nameservers
|
||||||
|
- enabled
|
||||||
|
- groups
|
||||||
|
- primary
|
||||||
|
- domains
|
||||||
|
NameserverGroup:
|
||||||
|
allOf:
|
||||||
|
- type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
description: Nameserver group ID
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- id
|
||||||
|
- $ref: '#/components/schemas/NameserverGroupRequest'
|
||||||
|
NameserverGroupPatchOperation:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/components/schemas/PatchMinimum'
|
||||||
|
- type: object
|
||||||
|
properties:
|
||||||
|
path:
|
||||||
|
description: Nameserver group field to update in form /<field>
|
||||||
|
type: string
|
||||||
|
enum: [ "name", "description", "enabled", "groups", "nameservers", "primary", "domains" ]
|
||||||
|
required:
|
||||||
|
- path
|
||||||
|
|
||||||
responses:
|
responses:
|
||||||
not_found:
|
not_found:
|
||||||
@@ -392,6 +541,67 @@ paths:
|
|||||||
"$ref": "#/components/responses/forbidden"
|
"$ref": "#/components/responses/forbidden"
|
||||||
'500':
|
'500':
|
||||||
"$ref": "#/components/responses/internal_error"
|
"$ref": "#/components/responses/internal_error"
|
||||||
|
/api/users/:
|
||||||
|
post:
|
||||||
|
summary: Create a User (invite)
|
||||||
|
tags: [ Users]
|
||||||
|
security:
|
||||||
|
- BearerAuth: [ ]
|
||||||
|
requestBody:
|
||||||
|
description: User invite information
|
||||||
|
content:
|
||||||
|
'application/json':
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/UserCreateRequest'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: A User object
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/User'
|
||||||
|
'400':
|
||||||
|
"$ref": "#/components/responses/bad_request"
|
||||||
|
'401':
|
||||||
|
"$ref": "#/components/responses/requires_authentication"
|
||||||
|
'403':
|
||||||
|
"$ref": "#/components/responses/forbidden"
|
||||||
|
'500':
|
||||||
|
"$ref": "#/components/responses/internal_error"
|
||||||
|
/api/users/{id}:
|
||||||
|
put:
|
||||||
|
summary: Update information about a User
|
||||||
|
tags: [ Users]
|
||||||
|
security:
|
||||||
|
- BearerAuth: [ ]
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
description: The User ID
|
||||||
|
requestBody:
|
||||||
|
description: User update
|
||||||
|
content:
|
||||||
|
'application/json':
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/UserRequest'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: A User object
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/User'
|
||||||
|
'400':
|
||||||
|
"$ref": "#/components/responses/bad_request"
|
||||||
|
'401':
|
||||||
|
"$ref": "#/components/responses/requires_authentication"
|
||||||
|
'403':
|
||||||
|
"$ref": "#/components/responses/forbidden"
|
||||||
|
'500':
|
||||||
|
"$ref": "#/components/responses/internal_error"
|
||||||
/api/peers:
|
/api/peers:
|
||||||
get:
|
get:
|
||||||
summary: Returns a list of all peers
|
summary: Returns a list of all peers
|
||||||
@@ -1169,6 +1379,176 @@ paths:
|
|||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
description: The Route ID
|
description: The Route ID
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Delete status code
|
||||||
|
content: { }
|
||||||
|
'400':
|
||||||
|
"$ref": "#/components/responses/bad_request"
|
||||||
|
'401':
|
||||||
|
"$ref": "#/components/responses/requires_authentication"
|
||||||
|
'403':
|
||||||
|
"$ref": "#/components/responses/forbidden"
|
||||||
|
'500':
|
||||||
|
"$ref": "#/components/responses/internal_error"
|
||||||
|
/api/dns/nameservers:
|
||||||
|
get:
|
||||||
|
summary: Returns a list of all Nameserver Groups
|
||||||
|
tags: [ DNS ]
|
||||||
|
security:
|
||||||
|
- BearerAuth: [ ]
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: A JSON Array of Nameserver Groups
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/NameserverGroup'
|
||||||
|
'400':
|
||||||
|
"$ref": "#/components/responses/bad_request"
|
||||||
|
'401':
|
||||||
|
"$ref": "#/components/responses/requires_authentication"
|
||||||
|
'403':
|
||||||
|
"$ref": "#/components/responses/forbidden"
|
||||||
|
'500':
|
||||||
|
"$ref": "#/components/responses/internal_error"
|
||||||
|
post:
|
||||||
|
summary: Creates a Nameserver Group
|
||||||
|
tags: [ DNS ]
|
||||||
|
security:
|
||||||
|
- BearerAuth: [ ]
|
||||||
|
requestBody:
|
||||||
|
description: New Nameserver Groups request
|
||||||
|
content:
|
||||||
|
'application/json':
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/NameserverGroupRequest'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: A Nameserver Groups Object
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/NameserverGroup'
|
||||||
|
'400':
|
||||||
|
"$ref": "#/components/responses/bad_request"
|
||||||
|
'401':
|
||||||
|
"$ref": "#/components/responses/requires_authentication"
|
||||||
|
'403':
|
||||||
|
"$ref": "#/components/responses/forbidden"
|
||||||
|
'500':
|
||||||
|
"$ref": "#/components/responses/internal_error"
|
||||||
|
|
||||||
|
/api/dns/nameservers/{id}:
|
||||||
|
get:
|
||||||
|
summary: Get information about a Nameserver Groups
|
||||||
|
tags: [ DNS ]
|
||||||
|
security:
|
||||||
|
- BearerAuth: [ ]
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
description: The Nameserver Group ID
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: A Nameserver Group object
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/NameserverGroup'
|
||||||
|
'400':
|
||||||
|
"$ref": "#/components/responses/bad_request"
|
||||||
|
'401':
|
||||||
|
"$ref": "#/components/responses/requires_authentication"
|
||||||
|
'403':
|
||||||
|
"$ref": "#/components/responses/forbidden"
|
||||||
|
'500':
|
||||||
|
"$ref": "#/components/responses/internal_error"
|
||||||
|
put:
|
||||||
|
summary: Update/Replace a Nameserver Group
|
||||||
|
tags: [ DNS ]
|
||||||
|
security:
|
||||||
|
- BearerAuth: [ ]
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
description: The Nameserver Group ID
|
||||||
|
requestBody:
|
||||||
|
description: Update Nameserver Group request
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/NameserverGroupRequest'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: A Nameserver Group object
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/NameserverGroup'
|
||||||
|
'400':
|
||||||
|
"$ref": "#/components/responses/bad_request"
|
||||||
|
'401':
|
||||||
|
"$ref": "#/components/responses/requires_authentication"
|
||||||
|
'403':
|
||||||
|
"$ref": "#/components/responses/forbidden"
|
||||||
|
'500':
|
||||||
|
"$ref": "#/components/responses/internal_error"
|
||||||
|
patch:
|
||||||
|
summary: Update information about a Nameserver Group
|
||||||
|
tags: [ DNS ]
|
||||||
|
security:
|
||||||
|
- BearerAuth: [ ]
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
description: The Nameserver Group ID
|
||||||
|
requestBody:
|
||||||
|
description: Update Nameserver Group request using a list of json patch objects
|
||||||
|
content:
|
||||||
|
'application/json':
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/NameserverGroupPatchOperation'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: A Nameserver Group object
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/NameserverGroup'
|
||||||
|
'400':
|
||||||
|
"$ref": "#/components/responses/bad_request"
|
||||||
|
'401':
|
||||||
|
"$ref": "#/components/responses/requires_authentication"
|
||||||
|
'403':
|
||||||
|
"$ref": "#/components/responses/forbidden"
|
||||||
|
'500':
|
||||||
|
"$ref": "#/components/responses/internal_error"
|
||||||
|
delete:
|
||||||
|
summary: Delete a Nameserver Group
|
||||||
|
tags: [ DNS ]
|
||||||
|
security:
|
||||||
|
- BearerAuth: [ ]
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
description: The Nameserver Group ID
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
description: Delete status code
|
description: Delete status code
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// Package api provides primitives to interact with the openapi HTTP API.
|
// Package api provides primitives to interact with the openapi HTTP API.
|
||||||
//
|
//
|
||||||
// Code generated by github.com/deepmap/oapi-codegen version v1.11.0 DO NOT EDIT.
|
// Code generated by github.com/deepmap/oapi-codegen version v1.11.1-0.20220912230023-4a1477f6a8ba DO NOT EDIT.
|
||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -24,6 +24,29 @@ const (
|
|||||||
GroupPatchOperationPathPeers GroupPatchOperationPath = "peers"
|
GroupPatchOperationPathPeers GroupPatchOperationPath = "peers"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Defines values for NameserverNsType.
|
||||||
|
const (
|
||||||
|
NameserverNsTypeUdp NameserverNsType = "udp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Defines values for NameserverGroupPatchOperationOp.
|
||||||
|
const (
|
||||||
|
NameserverGroupPatchOperationOpAdd NameserverGroupPatchOperationOp = "add"
|
||||||
|
NameserverGroupPatchOperationOpRemove NameserverGroupPatchOperationOp = "remove"
|
||||||
|
NameserverGroupPatchOperationOpReplace NameserverGroupPatchOperationOp = "replace"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Defines values for NameserverGroupPatchOperationPath.
|
||||||
|
const (
|
||||||
|
NameserverGroupPatchOperationPathDescription NameserverGroupPatchOperationPath = "description"
|
||||||
|
NameserverGroupPatchOperationPathDomains NameserverGroupPatchOperationPath = "domains"
|
||||||
|
NameserverGroupPatchOperationPathEnabled NameserverGroupPatchOperationPath = "enabled"
|
||||||
|
NameserverGroupPatchOperationPathGroups NameserverGroupPatchOperationPath = "groups"
|
||||||
|
NameserverGroupPatchOperationPathName NameserverGroupPatchOperationPath = "name"
|
||||||
|
NameserverGroupPatchOperationPathNameservers NameserverGroupPatchOperationPath = "nameservers"
|
||||||
|
NameserverGroupPatchOperationPathPrimary NameserverGroupPatchOperationPath = "primary"
|
||||||
|
)
|
||||||
|
|
||||||
// Defines values for PatchMinimumOp.
|
// Defines values for PatchMinimumOp.
|
||||||
const (
|
const (
|
||||||
PatchMinimumOpAdd PatchMinimumOp = "add"
|
PatchMinimumOpAdd PatchMinimumOp = "add"
|
||||||
@@ -66,300 +89,442 @@ const (
|
|||||||
RulePatchOperationPathSources RulePatchOperationPath = "sources"
|
RulePatchOperationPathSources RulePatchOperationPath = "sources"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Defines values for UserStatus.
|
||||||
|
const (
|
||||||
|
UserStatusActive UserStatus = "active"
|
||||||
|
UserStatusDisabled UserStatus = "disabled"
|
||||||
|
UserStatusInvited UserStatus = "invited"
|
||||||
|
)
|
||||||
|
|
||||||
// Group defines model for Group.
|
// Group defines model for Group.
|
||||||
type Group struct {
|
type Group struct {
|
||||||
// Group ID
|
// Id Group ID
|
||||||
Id string `json:"id"`
|
Id string `json:"id"`
|
||||||
|
|
||||||
// Group Name identifier
|
// Name Group Name identifier
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
|
||||||
// List of peers object
|
// Peers List of peers object
|
||||||
Peers []PeerMinimum `json:"peers"`
|
Peers []PeerMinimum `json:"peers"`
|
||||||
|
|
||||||
// Count of peers associated to the group
|
// PeersCount Count of peers associated to the group
|
||||||
PeersCount int `json:"peers_count"`
|
PeersCount int `json:"peers_count"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GroupMinimum defines model for GroupMinimum.
|
// GroupMinimum defines model for GroupMinimum.
|
||||||
type GroupMinimum struct {
|
type GroupMinimum struct {
|
||||||
// Group ID
|
// Id Group ID
|
||||||
Id string `json:"id"`
|
Id string `json:"id"`
|
||||||
|
|
||||||
// Group Name identifier
|
// Name Group Name identifier
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
|
||||||
// Count of peers associated to the group
|
// PeersCount Count of peers associated to the group
|
||||||
PeersCount int `json:"peers_count"`
|
PeersCount int `json:"peers_count"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GroupPatchOperation defines model for GroupPatchOperation.
|
// GroupPatchOperation defines model for GroupPatchOperation.
|
||||||
type GroupPatchOperation struct {
|
type GroupPatchOperation struct {
|
||||||
// Patch operation type
|
// Op Patch operation type
|
||||||
Op GroupPatchOperationOp `json:"op"`
|
Op GroupPatchOperationOp `json:"op"`
|
||||||
|
|
||||||
// Group field to update in form /<field>
|
// Path Group field to update in form /<field>
|
||||||
Path GroupPatchOperationPath `json:"path"`
|
Path GroupPatchOperationPath `json:"path"`
|
||||||
|
|
||||||
// Values to be applied
|
// Value Values to be applied
|
||||||
Value []string `json:"value"`
|
Value []string `json:"value"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Patch operation type
|
// GroupPatchOperationOp Patch operation type
|
||||||
type GroupPatchOperationOp string
|
type GroupPatchOperationOp string
|
||||||
|
|
||||||
// Group field to update in form /<field>
|
// GroupPatchOperationPath Group field to update in form /<field>
|
||||||
type GroupPatchOperationPath string
|
type GroupPatchOperationPath string
|
||||||
|
|
||||||
|
// Nameserver defines model for Nameserver.
|
||||||
|
type Nameserver struct {
|
||||||
|
// Ip Nameserver IP
|
||||||
|
Ip string `json:"ip"`
|
||||||
|
|
||||||
|
// NsType Nameserver Type
|
||||||
|
NsType NameserverNsType `json:"ns_type"`
|
||||||
|
|
||||||
|
// Port Nameserver Port
|
||||||
|
Port int `json:"port"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NameserverNsType Nameserver Type
|
||||||
|
type NameserverNsType string
|
||||||
|
|
||||||
|
// NameserverGroup defines model for NameserverGroup.
|
||||||
|
type NameserverGroup struct {
|
||||||
|
// Description Nameserver group description
|
||||||
|
Description string `json:"description"`
|
||||||
|
|
||||||
|
// Domains Nameserver group domain list
|
||||||
|
Domains []string `json:"domains"`
|
||||||
|
|
||||||
|
// Enabled Nameserver group status
|
||||||
|
Enabled bool `json:"enabled"`
|
||||||
|
|
||||||
|
// Groups Nameserver group tag groups
|
||||||
|
Groups []string `json:"groups"`
|
||||||
|
|
||||||
|
// Id Nameserver group ID
|
||||||
|
Id string `json:"id"`
|
||||||
|
|
||||||
|
// Name Nameserver group name
|
||||||
|
Name string `json:"name"`
|
||||||
|
|
||||||
|
// Nameservers Nameserver group
|
||||||
|
Nameservers []Nameserver `json:"nameservers"`
|
||||||
|
|
||||||
|
// Primary Nameserver group primary status
|
||||||
|
Primary bool `json:"primary"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NameserverGroupPatchOperation defines model for NameserverGroupPatchOperation.
|
||||||
|
type NameserverGroupPatchOperation struct {
|
||||||
|
// Op Patch operation type
|
||||||
|
Op NameserverGroupPatchOperationOp `json:"op"`
|
||||||
|
|
||||||
|
// Path Nameserver group field to update in form /<field>
|
||||||
|
Path NameserverGroupPatchOperationPath `json:"path"`
|
||||||
|
|
||||||
|
// Value Values to be applied
|
||||||
|
Value []string `json:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NameserverGroupPatchOperationOp Patch operation type
|
||||||
|
type NameserverGroupPatchOperationOp string
|
||||||
|
|
||||||
|
// NameserverGroupPatchOperationPath Nameserver group field to update in form /<field>
|
||||||
|
type NameserverGroupPatchOperationPath string
|
||||||
|
|
||||||
|
// NameserverGroupRequest defines model for NameserverGroupRequest.
|
||||||
|
type NameserverGroupRequest struct {
|
||||||
|
// Description Nameserver group description
|
||||||
|
Description string `json:"description"`
|
||||||
|
|
||||||
|
// Domains Nameserver group domain list
|
||||||
|
Domains []string `json:"domains"`
|
||||||
|
|
||||||
|
// Enabled Nameserver group status
|
||||||
|
Enabled bool `json:"enabled"`
|
||||||
|
|
||||||
|
// Groups Nameserver group tag groups
|
||||||
|
Groups []string `json:"groups"`
|
||||||
|
|
||||||
|
// Name Nameserver group name
|
||||||
|
Name string `json:"name"`
|
||||||
|
|
||||||
|
// Nameservers Nameserver group
|
||||||
|
Nameservers []Nameserver `json:"nameservers"`
|
||||||
|
|
||||||
|
// Primary Nameserver group primary status
|
||||||
|
Primary bool `json:"primary"`
|
||||||
|
}
|
||||||
|
|
||||||
// PatchMinimum defines model for PatchMinimum.
|
// PatchMinimum defines model for PatchMinimum.
|
||||||
type PatchMinimum struct {
|
type PatchMinimum struct {
|
||||||
// Patch operation type
|
// Op Patch operation type
|
||||||
Op PatchMinimumOp `json:"op"`
|
Op PatchMinimumOp `json:"op"`
|
||||||
|
|
||||||
// Values to be applied
|
// Value Values to be applied
|
||||||
Value []string `json:"value"`
|
Value []string `json:"value"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Patch operation type
|
// PatchMinimumOp Patch operation type
|
||||||
type PatchMinimumOp string
|
type PatchMinimumOp string
|
||||||
|
|
||||||
// Peer defines model for Peer.
|
// Peer defines model for Peer.
|
||||||
type Peer struct {
|
type Peer struct {
|
||||||
// Provides information of who activated the Peer. User or Setup Key
|
// Connected Peer to Management connection status
|
||||||
ActivatedBy struct {
|
|
||||||
Type string `json:"type"`
|
|
||||||
Value string `json:"value"`
|
|
||||||
} `json:"activated_by"`
|
|
||||||
|
|
||||||
// Peer to Management connection status
|
|
||||||
Connected bool `json:"connected"`
|
Connected bool `json:"connected"`
|
||||||
|
|
||||||
// Groups that the peer belongs to
|
// DnsLabel Peer's DNS label is the parsed peer name for domain resolution. It is used to form an FQDN by appending the account's domain to the peer label. e.g. peer-dns-label.netbird.cloud
|
||||||
|
DnsLabel string `json:"dns_label"`
|
||||||
|
|
||||||
|
// Groups Groups that the peer belongs to
|
||||||
Groups []GroupMinimum `json:"groups"`
|
Groups []GroupMinimum `json:"groups"`
|
||||||
|
|
||||||
// Peer ID
|
// Hostname Hostname of the machine
|
||||||
|
Hostname string `json:"hostname"`
|
||||||
|
|
||||||
|
// Id Peer ID
|
||||||
Id string `json:"id"`
|
Id string `json:"id"`
|
||||||
|
|
||||||
// Peer's IP address
|
// Ip Peer's IP address
|
||||||
Ip string `json:"ip"`
|
Ip string `json:"ip"`
|
||||||
|
|
||||||
// Last time peer connected to Netbird's management service
|
// LastSeen Last time peer connected to Netbird's management service
|
||||||
LastSeen time.Time `json:"last_seen"`
|
LastSeen time.Time `json:"last_seen"`
|
||||||
|
|
||||||
// Peer's hostname
|
// Name Peer's hostname
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
|
||||||
// Peer's operating system and version
|
// Os Peer's operating system and version
|
||||||
Os string `json:"os"`
|
Os string `json:"os"`
|
||||||
|
|
||||||
// Indicates whether SSH server is enabled on this peer
|
// SshEnabled Indicates whether SSH server is enabled on this peer
|
||||||
SshEnabled bool `json:"ssh_enabled"`
|
SshEnabled bool `json:"ssh_enabled"`
|
||||||
|
|
||||||
// Peer's daemon or cli version
|
// UiVersion Peer's desktop UI version
|
||||||
|
UiVersion *string `json:"ui_version,omitempty"`
|
||||||
|
|
||||||
|
// UserId User ID of the user that enrolled this peer
|
||||||
|
UserId *string `json:"user_id,omitempty"`
|
||||||
|
|
||||||
|
// Version Peer's daemon or cli version
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// PeerMinimum defines model for PeerMinimum.
|
// PeerMinimum defines model for PeerMinimum.
|
||||||
type PeerMinimum struct {
|
type PeerMinimum struct {
|
||||||
// Peer ID
|
// Id Peer ID
|
||||||
Id string `json:"id"`
|
Id string `json:"id"`
|
||||||
|
|
||||||
// Peer's hostname
|
// Name Peer's hostname
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Route defines model for Route.
|
// Route defines model for Route.
|
||||||
type Route struct {
|
type Route struct {
|
||||||
// Route description
|
// Description Route description
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
|
|
||||||
// Route status
|
// Enabled Route status
|
||||||
Enabled bool `json:"enabled"`
|
Enabled bool `json:"enabled"`
|
||||||
|
|
||||||
// Route Id
|
// Id Route Id
|
||||||
Id string `json:"id"`
|
Id string `json:"id"`
|
||||||
|
|
||||||
// Indicate if peer should masquerade traffic to this route's prefix
|
// Masquerade Indicate if peer should masquerade traffic to this route's prefix
|
||||||
Masquerade bool `json:"masquerade"`
|
Masquerade bool `json:"masquerade"`
|
||||||
|
|
||||||
// Route metric number. Lowest number has higher priority
|
// Metric Route metric number. Lowest number has higher priority
|
||||||
Metric int `json:"metric"`
|
Metric int `json:"metric"`
|
||||||
|
|
||||||
// Network range in CIDR format
|
// Network Network range in CIDR format
|
||||||
Network string `json:"network"`
|
Network string `json:"network"`
|
||||||
|
|
||||||
// Route network identifier, to group HA routes
|
// NetworkId Route network identifier, to group HA routes
|
||||||
NetworkId string `json:"network_id"`
|
NetworkId string `json:"network_id"`
|
||||||
|
|
||||||
// Network type indicating if it is IPv4 or IPv6
|
// NetworkType Network type indicating if it is IPv4 or IPv6
|
||||||
NetworkType string `json:"network_type"`
|
NetworkType string `json:"network_type"`
|
||||||
|
|
||||||
// Peer Identifier associated with route
|
// Peer Peer Identifier associated with route
|
||||||
Peer string `json:"peer"`
|
Peer string `json:"peer"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// RoutePatchOperation defines model for RoutePatchOperation.
|
// RoutePatchOperation defines model for RoutePatchOperation.
|
||||||
type RoutePatchOperation struct {
|
type RoutePatchOperation struct {
|
||||||
// Patch operation type
|
// Op Patch operation type
|
||||||
Op RoutePatchOperationOp `json:"op"`
|
Op RoutePatchOperationOp `json:"op"`
|
||||||
|
|
||||||
// Route field to update in form /<field>
|
// Path Route field to update in form /<field>
|
||||||
Path RoutePatchOperationPath `json:"path"`
|
Path RoutePatchOperationPath `json:"path"`
|
||||||
|
|
||||||
// Values to be applied
|
// Value Values to be applied
|
||||||
Value []string `json:"value"`
|
Value []string `json:"value"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Patch operation type
|
// RoutePatchOperationOp Patch operation type
|
||||||
type RoutePatchOperationOp string
|
type RoutePatchOperationOp string
|
||||||
|
|
||||||
// Route field to update in form /<field>
|
// RoutePatchOperationPath Route field to update in form /<field>
|
||||||
type RoutePatchOperationPath string
|
type RoutePatchOperationPath string
|
||||||
|
|
||||||
// RouteRequest defines model for RouteRequest.
|
// RouteRequest defines model for RouteRequest.
|
||||||
type RouteRequest struct {
|
type RouteRequest struct {
|
||||||
// Route description
|
// Description Route description
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
|
|
||||||
// Route status
|
// Enabled Route status
|
||||||
Enabled bool `json:"enabled"`
|
Enabled bool `json:"enabled"`
|
||||||
|
|
||||||
// Indicate if peer should masquerade traffic to this route's prefix
|
// Masquerade Indicate if peer should masquerade traffic to this route's prefix
|
||||||
Masquerade bool `json:"masquerade"`
|
Masquerade bool `json:"masquerade"`
|
||||||
|
|
||||||
// Route metric number. Lowest number has higher priority
|
// Metric Route metric number. Lowest number has higher priority
|
||||||
Metric int `json:"metric"`
|
Metric int `json:"metric"`
|
||||||
|
|
||||||
// Network range in CIDR format
|
// Network Network range in CIDR format
|
||||||
Network string `json:"network"`
|
Network string `json:"network"`
|
||||||
|
|
||||||
// Route network identifier, to group HA routes
|
// NetworkId Route network identifier, to group HA routes
|
||||||
NetworkId string `json:"network_id"`
|
NetworkId string `json:"network_id"`
|
||||||
|
|
||||||
// Peer Identifier associated with route
|
// Peer Peer Identifier associated with route
|
||||||
Peer string `json:"peer"`
|
Peer string `json:"peer"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rule defines model for Rule.
|
// Rule defines model for Rule.
|
||||||
type Rule struct {
|
type Rule struct {
|
||||||
// Rule friendly description
|
// Description Rule friendly description
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
|
|
||||||
// Rule destination groups
|
// Destinations Rule destination groups
|
||||||
Destinations []GroupMinimum `json:"destinations"`
|
Destinations []GroupMinimum `json:"destinations"`
|
||||||
|
|
||||||
// Rules status
|
// Disabled Rules status
|
||||||
Disabled bool `json:"disabled"`
|
Disabled bool `json:"disabled"`
|
||||||
|
|
||||||
// Rule flow, currently, only "bidirect" for bi-directional traffic is accepted
|
// Flow Rule flow, currently, only "bidirect" for bi-directional traffic is accepted
|
||||||
Flow string `json:"flow"`
|
Flow string `json:"flow"`
|
||||||
|
|
||||||
// Rule ID
|
// Id Rule ID
|
||||||
Id string `json:"id"`
|
Id string `json:"id"`
|
||||||
|
|
||||||
// Rule name identifier
|
// Name Rule name identifier
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
|
||||||
// Rule source groups
|
// Sources Rule source groups
|
||||||
Sources []GroupMinimum `json:"sources"`
|
Sources []GroupMinimum `json:"sources"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// RuleMinimum defines model for RuleMinimum.
|
// RuleMinimum defines model for RuleMinimum.
|
||||||
type RuleMinimum struct {
|
type RuleMinimum struct {
|
||||||
// Rule friendly description
|
// Description Rule friendly description
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
|
|
||||||
// Rules status
|
// Disabled Rules status
|
||||||
Disabled bool `json:"disabled"`
|
Disabled bool `json:"disabled"`
|
||||||
|
|
||||||
// Rule flow, currently, only "bidirect" for bi-directional traffic is accepted
|
// Flow Rule flow, currently, only "bidirect" for bi-directional traffic is accepted
|
||||||
Flow string `json:"flow"`
|
Flow string `json:"flow"`
|
||||||
|
|
||||||
// Rule name identifier
|
// Name Rule name identifier
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// RulePatchOperation defines model for RulePatchOperation.
|
// RulePatchOperation defines model for RulePatchOperation.
|
||||||
type RulePatchOperation struct {
|
type RulePatchOperation struct {
|
||||||
// Patch operation type
|
// Op Patch operation type
|
||||||
Op RulePatchOperationOp `json:"op"`
|
Op RulePatchOperationOp `json:"op"`
|
||||||
|
|
||||||
// Rule field to update in form /<field>
|
// Path Rule field to update in form /<field>
|
||||||
Path RulePatchOperationPath `json:"path"`
|
Path RulePatchOperationPath `json:"path"`
|
||||||
|
|
||||||
// Values to be applied
|
// Value Values to be applied
|
||||||
Value []string `json:"value"`
|
Value []string `json:"value"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Patch operation type
|
// RulePatchOperationOp Patch operation type
|
||||||
type RulePatchOperationOp string
|
type RulePatchOperationOp string
|
||||||
|
|
||||||
// Rule field to update in form /<field>
|
// RulePatchOperationPath Rule field to update in form /<field>
|
||||||
type RulePatchOperationPath string
|
type RulePatchOperationPath string
|
||||||
|
|
||||||
// SetupKey defines model for SetupKey.
|
// SetupKey defines model for SetupKey.
|
||||||
type SetupKey struct {
|
type SetupKey struct {
|
||||||
// Setup Key expiration date
|
// AutoGroups Setup key groups to auto-assign to peers registered with this key
|
||||||
|
AutoGroups []string `json:"auto_groups"`
|
||||||
|
|
||||||
|
// Expires Setup Key expiration date
|
||||||
Expires time.Time `json:"expires"`
|
Expires time.Time `json:"expires"`
|
||||||
|
|
||||||
// Setup Key ID
|
// Id Setup Key ID
|
||||||
Id string `json:"id"`
|
Id string `json:"id"`
|
||||||
|
|
||||||
// Setup Key value
|
// Key Setup Key value
|
||||||
Key string `json:"key"`
|
Key string `json:"key"`
|
||||||
|
|
||||||
// Setup key last usage date
|
// LastUsed Setup key last usage date
|
||||||
LastUsed time.Time `json:"last_used"`
|
LastUsed time.Time `json:"last_used"`
|
||||||
|
|
||||||
// Setup key name identifier
|
// Name Setup key name identifier
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
|
||||||
// Setup key revocation status
|
// Revoked Setup key revocation status
|
||||||
Revoked bool `json:"revoked"`
|
Revoked bool `json:"revoked"`
|
||||||
|
|
||||||
// Setup key status, "valid", "overused","expired" or "revoked"
|
// State Setup key status, "valid", "overused","expired" or "revoked"
|
||||||
State string `json:"state"`
|
State string `json:"state"`
|
||||||
|
|
||||||
// Setup key type, one-off for single time usage and reusable
|
// Type Setup key type, one-off for single time usage and reusable
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
|
|
||||||
// Usage count of setup key
|
// UpdatedAt Setup key last update date
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
|
||||||
|
// UsedTimes Usage count of setup key
|
||||||
UsedTimes int `json:"used_times"`
|
UsedTimes int `json:"used_times"`
|
||||||
|
|
||||||
// Setup key validity status
|
// Valid Setup key validity status
|
||||||
Valid bool `json:"valid"`
|
Valid bool `json:"valid"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetupKeyRequest defines model for SetupKeyRequest.
|
// SetupKeyRequest defines model for SetupKeyRequest.
|
||||||
type SetupKeyRequest struct {
|
type SetupKeyRequest struct {
|
||||||
// Expiration time in seconds
|
// AutoGroups Setup key groups to auto-assign to peers registered with this key
|
||||||
|
AutoGroups []string `json:"auto_groups"`
|
||||||
|
|
||||||
|
// ExpiresIn Expiration time in seconds
|
||||||
ExpiresIn int `json:"expires_in"`
|
ExpiresIn int `json:"expires_in"`
|
||||||
|
|
||||||
// Setup Key name
|
// Name Setup Key name
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
|
||||||
// Setup key revocation status
|
// Revoked Setup key revocation status
|
||||||
Revoked bool `json:"revoked"`
|
Revoked bool `json:"revoked"`
|
||||||
|
|
||||||
// Setup key type, one-off for single time usage and reusable
|
// Type Setup key type, one-off for single time usage and reusable
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// User defines model for User.
|
// User defines model for User.
|
||||||
type User struct {
|
type User struct {
|
||||||
// User's email address
|
// AutoGroups Groups to auto-assign to peers registered by this user
|
||||||
|
AutoGroups []string `json:"auto_groups"`
|
||||||
|
|
||||||
|
// Email User's email address
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
|
|
||||||
// User ID
|
// Id User ID
|
||||||
Id string `json:"id"`
|
Id string `json:"id"`
|
||||||
|
|
||||||
// User's name from idp provider
|
// Name User's name from idp provider
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
|
||||||
// User's Netbird account role
|
// Role User's NetBird account role
|
||||||
|
Role string `json:"role"`
|
||||||
|
|
||||||
|
// Status User's status
|
||||||
|
Status UserStatus `json:"status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserStatus User's status
|
||||||
|
type UserStatus string
|
||||||
|
|
||||||
|
// UserCreateRequest defines model for UserCreateRequest.
|
||||||
|
type UserCreateRequest struct {
|
||||||
|
// AutoGroups Groups to auto-assign to peers registered by this user
|
||||||
|
AutoGroups []string `json:"auto_groups"`
|
||||||
|
|
||||||
|
// Email User's Email to send invite to
|
||||||
|
Email string `json:"email"`
|
||||||
|
|
||||||
|
// Name User's full name
|
||||||
|
Name *string `json:"name,omitempty"`
|
||||||
|
|
||||||
|
// Role User's NetBird account role
|
||||||
Role string `json:"role"`
|
Role string `json:"role"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UserRequest defines model for UserRequest.
|
||||||
|
type UserRequest struct {
|
||||||
|
// AutoGroups Groups to auto-assign to peers registered by this user
|
||||||
|
AutoGroups []string `json:"auto_groups"`
|
||||||
|
|
||||||
|
// Role User's NetBird account role
|
||||||
|
Role string `json:"role"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PatchApiDnsNameserversIdJSONBody defines parameters for PatchApiDnsNameserversId.
|
||||||
|
type PatchApiDnsNameserversIdJSONBody = []NameserverGroupPatchOperation
|
||||||
|
|
||||||
// PostApiGroupsJSONBody defines parameters for PostApiGroups.
|
// PostApiGroupsJSONBody defines parameters for PostApiGroups.
|
||||||
type PostApiGroupsJSONBody struct {
|
type PostApiGroupsJSONBody struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
@@ -381,28 +546,22 @@ type PutApiPeersIdJSONBody struct {
|
|||||||
SshEnabled bool `json:"ssh_enabled"`
|
SshEnabled bool `json:"ssh_enabled"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// PostApiRoutesJSONBody defines parameters for PostApiRoutes.
|
|
||||||
type PostApiRoutesJSONBody = RouteRequest
|
|
||||||
|
|
||||||
// PatchApiRoutesIdJSONBody defines parameters for PatchApiRoutesId.
|
// PatchApiRoutesIdJSONBody defines parameters for PatchApiRoutesId.
|
||||||
type PatchApiRoutesIdJSONBody = []RoutePatchOperation
|
type PatchApiRoutesIdJSONBody = []RoutePatchOperation
|
||||||
|
|
||||||
// PutApiRoutesIdJSONBody defines parameters for PutApiRoutesId.
|
|
||||||
type PutApiRoutesIdJSONBody = RouteRequest
|
|
||||||
|
|
||||||
// PostApiRulesJSONBody defines parameters for PostApiRules.
|
// PostApiRulesJSONBody defines parameters for PostApiRules.
|
||||||
type PostApiRulesJSONBody struct {
|
type PostApiRulesJSONBody struct {
|
||||||
// Rule friendly description
|
// Description Rule friendly description
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
Destinations *[]string `json:"destinations,omitempty"`
|
Destinations *[]string `json:"destinations,omitempty"`
|
||||||
|
|
||||||
// Rules status
|
// Disabled Rules status
|
||||||
Disabled bool `json:"disabled"`
|
Disabled bool `json:"disabled"`
|
||||||
|
|
||||||
// Rule flow, currently, only "bidirect" for bi-directional traffic is accepted
|
// Flow Rule flow, currently, only "bidirect" for bi-directional traffic is accepted
|
||||||
Flow string `json:"flow"`
|
Flow string `json:"flow"`
|
||||||
|
|
||||||
// Rule name identifier
|
// Name Rule name identifier
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Sources *[]string `json:"sources,omitempty"`
|
Sources *[]string `json:"sources,omitempty"`
|
||||||
}
|
}
|
||||||
@@ -412,26 +571,29 @@ type PatchApiRulesIdJSONBody = []RulePatchOperation
|
|||||||
|
|
||||||
// PutApiRulesIdJSONBody defines parameters for PutApiRulesId.
|
// PutApiRulesIdJSONBody defines parameters for PutApiRulesId.
|
||||||
type PutApiRulesIdJSONBody struct {
|
type PutApiRulesIdJSONBody struct {
|
||||||
// Rule friendly description
|
// Description Rule friendly description
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
Destinations *[]string `json:"destinations,omitempty"`
|
Destinations *[]string `json:"destinations,omitempty"`
|
||||||
|
|
||||||
// Rules status
|
// Disabled Rules status
|
||||||
Disabled bool `json:"disabled"`
|
Disabled bool `json:"disabled"`
|
||||||
|
|
||||||
// Rule flow, currently, only "bidirect" for bi-directional traffic is accepted
|
// Flow Rule flow, currently, only "bidirect" for bi-directional traffic is accepted
|
||||||
Flow string `json:"flow"`
|
Flow string `json:"flow"`
|
||||||
|
|
||||||
// Rule name identifier
|
// Name Rule name identifier
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Sources *[]string `json:"sources,omitempty"`
|
Sources *[]string `json:"sources,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// PostApiSetupKeysJSONBody defines parameters for PostApiSetupKeys.
|
// PostApiDnsNameserversJSONRequestBody defines body for PostApiDnsNameservers for application/json ContentType.
|
||||||
type PostApiSetupKeysJSONBody = SetupKeyRequest
|
type PostApiDnsNameserversJSONRequestBody = NameserverGroupRequest
|
||||||
|
|
||||||
// PutApiSetupKeysIdJSONBody defines parameters for PutApiSetupKeysId.
|
// PatchApiDnsNameserversIdJSONRequestBody defines body for PatchApiDnsNameserversId for application/json ContentType.
|
||||||
type PutApiSetupKeysIdJSONBody = SetupKeyRequest
|
type PatchApiDnsNameserversIdJSONRequestBody = PatchApiDnsNameserversIdJSONBody
|
||||||
|
|
||||||
|
// PutApiDnsNameserversIdJSONRequestBody defines body for PutApiDnsNameserversId for application/json ContentType.
|
||||||
|
type PutApiDnsNameserversIdJSONRequestBody = NameserverGroupRequest
|
||||||
|
|
||||||
// PostApiGroupsJSONRequestBody defines body for PostApiGroups for application/json ContentType.
|
// PostApiGroupsJSONRequestBody defines body for PostApiGroups for application/json ContentType.
|
||||||
type PostApiGroupsJSONRequestBody PostApiGroupsJSONBody
|
type PostApiGroupsJSONRequestBody PostApiGroupsJSONBody
|
||||||
@@ -446,13 +608,13 @@ type PutApiGroupsIdJSONRequestBody PutApiGroupsIdJSONBody
|
|||||||
type PutApiPeersIdJSONRequestBody PutApiPeersIdJSONBody
|
type PutApiPeersIdJSONRequestBody PutApiPeersIdJSONBody
|
||||||
|
|
||||||
// PostApiRoutesJSONRequestBody defines body for PostApiRoutes for application/json ContentType.
|
// PostApiRoutesJSONRequestBody defines body for PostApiRoutes for application/json ContentType.
|
||||||
type PostApiRoutesJSONRequestBody = PostApiRoutesJSONBody
|
type PostApiRoutesJSONRequestBody = RouteRequest
|
||||||
|
|
||||||
// PatchApiRoutesIdJSONRequestBody defines body for PatchApiRoutesId for application/json ContentType.
|
// PatchApiRoutesIdJSONRequestBody defines body for PatchApiRoutesId for application/json ContentType.
|
||||||
type PatchApiRoutesIdJSONRequestBody = PatchApiRoutesIdJSONBody
|
type PatchApiRoutesIdJSONRequestBody = PatchApiRoutesIdJSONBody
|
||||||
|
|
||||||
// PutApiRoutesIdJSONRequestBody defines body for PutApiRoutesId for application/json ContentType.
|
// PutApiRoutesIdJSONRequestBody defines body for PutApiRoutesId for application/json ContentType.
|
||||||
type PutApiRoutesIdJSONRequestBody = PutApiRoutesIdJSONBody
|
type PutApiRoutesIdJSONRequestBody = RouteRequest
|
||||||
|
|
||||||
// PostApiRulesJSONRequestBody defines body for PostApiRules for application/json ContentType.
|
// PostApiRulesJSONRequestBody defines body for PostApiRules for application/json ContentType.
|
||||||
type PostApiRulesJSONRequestBody PostApiRulesJSONBody
|
type PostApiRulesJSONRequestBody PostApiRulesJSONBody
|
||||||
@@ -464,7 +626,13 @@ type PatchApiRulesIdJSONRequestBody = PatchApiRulesIdJSONBody
|
|||||||
type PutApiRulesIdJSONRequestBody PutApiRulesIdJSONBody
|
type PutApiRulesIdJSONRequestBody PutApiRulesIdJSONBody
|
||||||
|
|
||||||
// PostApiSetupKeysJSONRequestBody defines body for PostApiSetupKeys for application/json ContentType.
|
// PostApiSetupKeysJSONRequestBody defines body for PostApiSetupKeys for application/json ContentType.
|
||||||
type PostApiSetupKeysJSONRequestBody = PostApiSetupKeysJSONBody
|
type PostApiSetupKeysJSONRequestBody = SetupKeyRequest
|
||||||
|
|
||||||
// PutApiSetupKeysIdJSONRequestBody defines body for PutApiSetupKeysId for application/json ContentType.
|
// PutApiSetupKeysIdJSONRequestBody defines body for PutApiSetupKeysId for application/json ContentType.
|
||||||
type PutApiSetupKeysIdJSONRequestBody = PutApiSetupKeysIdJSONBody
|
type PutApiSetupKeysIdJSONRequestBody = SetupKeyRequest
|
||||||
|
|
||||||
|
// PostApiUsersJSONRequestBody defines body for PostApiUsers for application/json ContentType.
|
||||||
|
type PostApiUsersJSONRequestBody = UserCreateRequest
|
||||||
|
|
||||||
|
// PutApiUsersIdJSONRequestBody defines body for PutApiUsersId for application/json ContentType.
|
||||||
|
type PutApiUsersIdJSONRequestBody = UserRequest
|
||||||
|
|||||||
@@ -2,10 +2,9 @@ package http
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
"github.com/netbirdio/netbird/management/server/http/api"
|
"github.com/netbirdio/netbird/management/server/http/api"
|
||||||
"google.golang.org/grpc/codes"
|
"github.com/netbirdio/netbird/management/server/http/util"
|
||||||
"google.golang.org/grpc/status"
|
"github.com/netbirdio/netbird/management/server/status"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/management/server"
|
"github.com/netbirdio/netbird/management/server"
|
||||||
@@ -33,7 +32,8 @@ func NewGroups(accountManager server.AccountManager, authAudience string) *Group
|
|||||||
|
|
||||||
// GetAllGroupsHandler list for the account
|
// GetAllGroupsHandler list for the account
|
||||||
func (h *Groups) GetAllGroupsHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *Groups) GetAllGroupsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
account, err := getJWTAccount(h.accountManager, h.jwtExtractor, h.authAudience, r)
|
claims := h.jwtExtractor.ExtractClaimsFromRequestContext(r, h.authAudience)
|
||||||
|
account, _, err := h.accountManager.GetAccountFromToken(claims)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||||
@@ -45,52 +45,54 @@ func (h *Groups) GetAllGroupsHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
groups = append(groups, toGroupResponse(account, g))
|
groups = append(groups, toGroupResponse(account, g))
|
||||||
}
|
}
|
||||||
|
|
||||||
writeJSONObject(w, groups)
|
util.WriteJSONObject(w, groups)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateGroupHandler handles update to a group identified by a given ID
|
// UpdateGroupHandler handles update to a group identified by a given ID
|
||||||
func (h *Groups) UpdateGroupHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *Groups) UpdateGroupHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
account, err := getJWTAccount(h.accountManager, h.jwtExtractor, h.authAudience, r)
|
claims := h.jwtExtractor.ExtractClaimsFromRequestContext(r, h.authAudience)
|
||||||
|
account, _, err := h.accountManager.GetAccountFromToken(claims)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
util.WriteError(err, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
groupID, ok := vars["id"]
|
groupID, ok := vars["id"]
|
||||||
if !ok {
|
if !ok {
|
||||||
http.Error(w, "group ID field is missing", http.StatusBadRequest)
|
util.WriteError(status.Errorf(status.InvalidArgument, "group ID field is missing"), w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if len(groupID) == 0 {
|
if len(groupID) == 0 {
|
||||||
http.Error(w, "group ID can't be empty", http.StatusUnprocessableEntity)
|
util.WriteError(status.Errorf(status.InvalidArgument, "group ID can't be empty"), w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_, ok = account.Groups[groupID]
|
_, ok = account.Groups[groupID]
|
||||||
if !ok {
|
if !ok {
|
||||||
http.Error(w, fmt.Sprintf("couldn't find group with ID %s", groupID), http.StatusNotFound)
|
util.WriteError(status.Errorf(status.NotFound, "couldn't find group with ID %s", groupID), w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
allGroup, err := account.GetGroupAll()
|
allGroup, err := account.GetGroupAll()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
util.WriteError(err, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if allGroup.ID == groupID {
|
if allGroup.ID == groupID {
|
||||||
http.Error(w, "updating group ALL is not allowed", http.StatusMethodNotAllowed)
|
util.WriteError(status.Errorf(status.InvalidArgument, "updating group ALL is not allowed"), w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var req api.PutApiGroupsIdJSONRequestBody
|
var req api.PutApiGroupsIdJSONRequestBody
|
||||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
err = json.NewDecoder(r.Body).Decode(&req)
|
||||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
if err != nil {
|
||||||
|
util.WriteErrorResponse("couldn't parse JSON request", http.StatusBadRequest, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if *req.Name == "" {
|
if *req.Name == "" {
|
||||||
http.Error(w, "group name shouldn't be empty", http.StatusUnprocessableEntity)
|
util.WriteError(status.Errorf(status.InvalidArgument, "group name shouldn't be empty"), w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,53 +104,55 @@ func (h *Groups) UpdateGroupHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
if err := h.accountManager.SaveGroup(account.Id, &group); err != nil {
|
if err := h.accountManager.SaveGroup(account.Id, &group); err != nil {
|
||||||
log.Errorf("failed updating group %s under account %s %v", groupID, account.Id, err)
|
log.Errorf("failed updating group %s under account %s %v", groupID, account.Id, err)
|
||||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
util.WriteError(err, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
writeJSONObject(w, toGroupResponse(account, &group))
|
util.WriteJSONObject(w, toGroupResponse(account, &group))
|
||||||
}
|
}
|
||||||
|
|
||||||
// PatchGroupHandler handles patch updates to a group identified by a given ID
|
// PatchGroupHandler handles patch updates to a group identified by a given ID
|
||||||
func (h *Groups) PatchGroupHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *Groups) PatchGroupHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
account, err := getJWTAccount(h.accountManager, h.jwtExtractor, h.authAudience, r)
|
claims := h.jwtExtractor.ExtractClaimsFromRequestContext(r, h.authAudience)
|
||||||
|
account, _, err := h.accountManager.GetAccountFromToken(claims)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
util.WriteError(err, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
groupID := vars["id"]
|
groupID := vars["id"]
|
||||||
if len(groupID) == 0 {
|
if len(groupID) == 0 {
|
||||||
http.Error(w, "invalid group Id", http.StatusBadRequest)
|
util.WriteError(status.Errorf(status.InvalidArgument, "invalid group ID"), w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_, ok := account.Groups[groupID]
|
_, ok := account.Groups[groupID]
|
||||||
if !ok {
|
if !ok {
|
||||||
http.Error(w, fmt.Sprintf("couldn't find group id %s", groupID), http.StatusNotFound)
|
util.WriteError(status.Errorf(status.NotFound, "couldn't find group ID %s", groupID), w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
allGroup, err := account.GetGroupAll()
|
allGroup, err := account.GetGroupAll()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
util.WriteError(err, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if allGroup.ID == groupID {
|
if allGroup.ID == groupID {
|
||||||
http.Error(w, "updating group ALL is not allowed", http.StatusMethodNotAllowed)
|
util.WriteError(status.Errorf(status.InvalidArgument, "updating group ALL is not allowed"), w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var req api.PatchApiGroupsIdJSONRequestBody
|
var req api.PatchApiGroupsIdJSONRequestBody
|
||||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
err = json.NewDecoder(r.Body).Decode(&req)
|
||||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
if err != nil {
|
||||||
|
util.WriteErrorResponse("couldn't parse JSON request", http.StatusBadRequest, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(req) == 0 {
|
if len(req) == 0 {
|
||||||
http.Error(w, "no patch instruction received", http.StatusBadRequest)
|
util.WriteError(status.Errorf(status.InvalidArgument, "no patch instruction received"), w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,13 +162,13 @@ func (h *Groups) PatchGroupHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
switch patch.Path {
|
switch patch.Path {
|
||||||
case api.GroupPatchOperationPathName:
|
case api.GroupPatchOperationPathName:
|
||||||
if patch.Op != api.GroupPatchOperationOpReplace {
|
if patch.Op != api.GroupPatchOperationOpReplace {
|
||||||
http.Error(w, fmt.Sprintf("Name field only accepts replace operation, got %s", patch.Op),
|
util.WriteError(status.Errorf(status.InvalidArgument,
|
||||||
http.StatusBadRequest)
|
"name field only accepts replace operation, got %s", patch.Op), w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(patch.Value) == 0 || patch.Value[0] == "" {
|
if len(patch.Value) == 0 || patch.Value[0] == "" {
|
||||||
http.Error(w, "Group name shouldn't be empty", http.StatusUnprocessableEntity)
|
util.WriteError(status.Errorf(status.InvalidArgument, "group name shouldn't be empty"), w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,53 +197,43 @@ func (h *Groups) PatchGroupHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
Values: peerKeys,
|
Values: peerKeys,
|
||||||
})
|
})
|
||||||
default:
|
default:
|
||||||
http.Error(w, "invalid operation, \"%s\", for Peers field", http.StatusBadRequest)
|
util.WriteError(status.Errorf(status.InvalidArgument,
|
||||||
|
"invalid operation, \"%v\", for Peers field", patch.Op), w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
http.Error(w, "invalid patch path", http.StatusBadRequest)
|
util.WriteError(status.Errorf(status.InvalidArgument, "invalid patch path"), w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
group, err := h.accountManager.UpdateGroup(account.Id, groupID, operations)
|
group, err := h.accountManager.UpdateGroup(account.Id, groupID, operations)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errStatus, ok := status.FromError(err)
|
util.WriteError(err, w)
|
||||||
if ok && errStatus.Code() == codes.Internal {
|
|
||||||
http.Error(w, errStatus.String(), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if ok && errStatus.Code() == codes.NotFound {
|
|
||||||
http.Error(w, errStatus.String(), http.StatusNotFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Errorf("failed updating group %s under account %s %v", groupID, account.Id, err)
|
|
||||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
writeJSONObject(w, toGroupResponse(account, group))
|
util.WriteJSONObject(w, toGroupResponse(account, group))
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateGroupHandler handles group creation request
|
// CreateGroupHandler handles group creation request
|
||||||
func (h *Groups) CreateGroupHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *Groups) CreateGroupHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
account, err := getJWTAccount(h.accountManager, h.jwtExtractor, h.authAudience, r)
|
claims := h.jwtExtractor.ExtractClaimsFromRequestContext(r, h.authAudience)
|
||||||
|
account, _, err := h.accountManager.GetAccountFromToken(claims)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
util.WriteError(err, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var req api.PostApiGroupsJSONRequestBody
|
var req api.PostApiGroupsJSONRequestBody
|
||||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
err = json.NewDecoder(r.Body).Decode(&req)
|
||||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
if err != nil {
|
||||||
|
util.WriteErrorResponse("couldn't parse JSON request", http.StatusBadRequest, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if req.Name == "" {
|
if req.Name == "" {
|
||||||
http.Error(w, "Group name shouldn't be empty", http.StatusUnprocessableEntity)
|
util.WriteError(status.Errorf(status.InvalidArgument, "group name shouldn't be empty"), w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -249,55 +243,57 @@ func (h *Groups) CreateGroupHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
Peers: peerIPsToKeys(account, req.Peers),
|
Peers: peerIPsToKeys(account, req.Peers),
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := h.accountManager.SaveGroup(account.Id, &group); err != nil {
|
err = h.accountManager.SaveGroup(account.Id, &group)
|
||||||
log.Errorf("failed creating group \"%s\" under account %s %v", req.Name, account.Id, err)
|
if err != nil {
|
||||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
util.WriteError(err, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
writeJSONObject(w, toGroupResponse(account, &group))
|
util.WriteJSONObject(w, toGroupResponse(account, &group))
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteGroupHandler handles group deletion request
|
// DeleteGroupHandler handles group deletion request
|
||||||
func (h *Groups) DeleteGroupHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *Groups) DeleteGroupHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
account, err := getJWTAccount(h.accountManager, h.jwtExtractor, h.authAudience, r)
|
claims := h.jwtExtractor.ExtractClaimsFromRequestContext(r, h.authAudience)
|
||||||
|
account, _, err := h.accountManager.GetAccountFromToken(claims)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
util.WriteError(err, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
aID := account.Id
|
aID := account.Id
|
||||||
|
|
||||||
groupID := mux.Vars(r)["id"]
|
groupID := mux.Vars(r)["id"]
|
||||||
if len(groupID) == 0 {
|
if len(groupID) == 0 {
|
||||||
http.Error(w, "invalid group ID", http.StatusBadRequest)
|
util.WriteError(status.Errorf(status.InvalidArgument, "invalid group ID"), w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
allGroup, err := account.GetGroupAll()
|
allGroup, err := account.GetGroupAll()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
util.WriteError(err, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if allGroup.ID == groupID {
|
if allGroup.ID == groupID {
|
||||||
http.Error(w, "deleting group ALL is not allowed", http.StatusMethodNotAllowed)
|
util.WriteError(status.Errorf(status.InvalidArgument, "deleting group ALL is not allowed"), w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := h.accountManager.DeleteGroup(aID, groupID); err != nil {
|
err = h.accountManager.DeleteGroup(aID, groupID)
|
||||||
log.Errorf("failed delete group %s under account %s %v", groupID, aID, err)
|
if err != nil {
|
||||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
util.WriteError(err, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
writeJSONObject(w, "")
|
util.WriteJSONObject(w, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetGroupHandler returns a group
|
// GetGroupHandler returns a group
|
||||||
func (h *Groups) GetGroupHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *Groups) GetGroupHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
account, err := getJWTAccount(h.accountManager, h.jwtExtractor, h.authAudience, r)
|
claims := h.jwtExtractor.ExtractClaimsFromRequestContext(r, h.authAudience)
|
||||||
|
account, _, err := h.accountManager.GetAccountFromToken(claims)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
util.WriteError(err, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -305,19 +301,22 @@ func (h *Groups) GetGroupHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
case http.MethodGet:
|
case http.MethodGet:
|
||||||
groupID := mux.Vars(r)["id"]
|
groupID := mux.Vars(r)["id"]
|
||||||
if len(groupID) == 0 {
|
if len(groupID) == 0 {
|
||||||
http.Error(w, "invalid group ID", http.StatusBadRequest)
|
util.WriteError(status.Errorf(status.InvalidArgument, "invalid group ID"), w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
group, err := h.accountManager.GetGroup(account.Id, groupID)
|
group, err := h.accountManager.GetGroup(account.Id, groupID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "group not found", http.StatusNotFound)
|
util.WriteError(err, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
writeJSONObject(w, toGroupResponse(account, group))
|
util.WriteJSONObject(w, toGroupResponse(account, group))
|
||||||
default:
|
default:
|
||||||
http.Error(w, "", http.StatusNotFound)
|
if err != nil {
|
||||||
|
util.WriteError(status.Errorf(status.NotFound, "HTTP method not found"), w)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/netbirdio/netbird/management/server/http/api"
|
"github.com/netbirdio/netbird/management/server/http/api"
|
||||||
|
"github.com/netbirdio/netbird/management/server/status"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -21,11 +22,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var TestPeers = map[string]*server.Peer{
|
var TestPeers = map[string]*server.Peer{
|
||||||
"A": &server.Peer{Key: "A", IP: net.ParseIP("100.100.100.100")},
|
"A": {Key: "A", IP: net.ParseIP("100.100.100.100")},
|
||||||
"B": &server.Peer{Key: "B", IP: net.ParseIP("200.200.200.200")},
|
"B": {Key: "B", IP: net.ParseIP("200.200.200.200")},
|
||||||
}
|
}
|
||||||
|
|
||||||
func initGroupTestData(groups ...*server.Group) *Groups {
|
func initGroupTestData(user *server.User, groups ...*server.Group) *Groups {
|
||||||
return &Groups{
|
return &Groups{
|
||||||
accountManager: &mock_server.MockAccountManager{
|
accountManager: &mock_server.MockAccountManager{
|
||||||
SaveGroupFunc: func(accountID string, group *server.Group) error {
|
SaveGroupFunc: func(accountID string, group *server.Group) error {
|
||||||
@@ -36,7 +37,7 @@ func initGroupTestData(groups ...*server.Group) *Groups {
|
|||||||
},
|
},
|
||||||
GetGroupFunc: func(_, groupID string) (*server.Group, error) {
|
GetGroupFunc: func(_, groupID string) (*server.Group, error) {
|
||||||
if groupID != "idofthegroup" {
|
if groupID != "idofthegroup" {
|
||||||
return nil, fmt.Errorf("not found")
|
return nil, status.Errorf(status.NotFound, "not found")
|
||||||
}
|
}
|
||||||
return &server.Group{
|
return &server.Group{
|
||||||
ID: "idofthegroup",
|
ID: "idofthegroup",
|
||||||
@@ -67,15 +68,18 @@ func initGroupTestData(groups ...*server.Group) *Groups {
|
|||||||
}
|
}
|
||||||
return nil, fmt.Errorf("peer not found")
|
return nil, fmt.Errorf("peer not found")
|
||||||
},
|
},
|
||||||
GetAccountWithAuthorizationClaimsFunc: func(claims jwtclaims.AuthorizationClaims) (*server.Account, error) {
|
GetAccountFromTokenFunc: func(claims jwtclaims.AuthorizationClaims) (*server.Account, *server.User, error) {
|
||||||
return &server.Account{
|
return &server.Account{
|
||||||
Id: claims.AccountId,
|
Id: claims.AccountId,
|
||||||
Domain: "hotmail.com",
|
Domain: "hotmail.com",
|
||||||
Peers: TestPeers,
|
Peers: TestPeers,
|
||||||
|
Users: map[string]*server.User{
|
||||||
|
user.Id: user,
|
||||||
|
},
|
||||||
Groups: map[string]*server.Group{
|
Groups: map[string]*server.Group{
|
||||||
"id-existed": &server.Group{ID: "id-existed", Peers: []string{"A", "B"}},
|
"id-existed": {ID: "id-existed", Peers: []string{"A", "B"}},
|
||||||
"id-all": &server.Group{ID: "id-all", Name: "All"}},
|
"id-all": {ID: "id-all", Name: "All"}},
|
||||||
}, nil
|
}, user, nil
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
authAudience: "",
|
authAudience: "",
|
||||||
@@ -120,7 +124,8 @@ func TestGetGroup(t *testing.T) {
|
|||||||
Name: "Group",
|
Name: "Group",
|
||||||
}
|
}
|
||||||
|
|
||||||
p := initGroupTestData(group)
|
adminUser := server.NewAdminUser("test_user")
|
||||||
|
p := initGroupTestData(adminUser, group)
|
||||||
|
|
||||||
for _, tc := range tt {
|
for _, tc := range tt {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
@@ -219,7 +224,7 @@ func TestWriteGroup(t *testing.T) {
|
|||||||
requestPath: "/api/groups/id-all",
|
requestPath: "/api/groups/id-all",
|
||||||
requestBody: bytes.NewBuffer(
|
requestBody: bytes.NewBuffer(
|
||||||
[]byte(`{"Name":"super"}`)),
|
[]byte(`{"Name":"super"}`)),
|
||||||
expectedStatus: http.StatusMethodNotAllowed,
|
expectedStatus: http.StatusUnprocessableEntity,
|
||||||
expectedBody: false,
|
expectedBody: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -240,7 +245,7 @@ func TestWriteGroup(t *testing.T) {
|
|||||||
requestPath: "/api/groups/id-existed",
|
requestPath: "/api/groups/id-existed",
|
||||||
requestBody: bytes.NewBuffer(
|
requestBody: bytes.NewBuffer(
|
||||||
[]byte(`[{"op":"insert","path":"name","value":[""]}]`)),
|
[]byte(`[{"op":"insert","path":"name","value":[""]}]`)),
|
||||||
expectedStatus: http.StatusBadRequest,
|
expectedStatus: http.StatusUnprocessableEntity,
|
||||||
expectedBody: false,
|
expectedBody: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -270,7 +275,8 @@ func TestWriteGroup(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
p := initGroupTestData()
|
adminUser := server.NewAdminUser("test_user")
|
||||||
|
p := initGroupTestData(adminUser)
|
||||||
|
|
||||||
for _, tc := range tt {
|
for _, tc := range tt {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
|||||||
@@ -4,12 +4,14 @@ import (
|
|||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
s "github.com/netbirdio/netbird/management/server"
|
s "github.com/netbirdio/netbird/management/server"
|
||||||
"github.com/netbirdio/netbird/management/server/http/middleware"
|
"github.com/netbirdio/netbird/management/server/http/middleware"
|
||||||
|
"github.com/netbirdio/netbird/management/server/telemetry"
|
||||||
"github.com/rs/cors"
|
"github.com/rs/cors"
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
// APIHandler creates the Management service HTTP API handler registering all the available endpoints.
|
// APIHandler creates the Management service HTTP API handler registering all the available endpoints.
|
||||||
func APIHandler(accountManager s.AccountManager, authIssuer string, authAudience string, authKeysLocation string) (http.Handler, error) {
|
func APIHandler(accountManager s.AccountManager, authIssuer string, authAudience string, authKeysLocation string,
|
||||||
|
appMetrics telemetry.AppMetrics) (http.Handler, error) {
|
||||||
jwtMiddleware, err := middleware.NewJwtMiddleware(
|
jwtMiddleware, err := middleware.NewJwtMiddleware(
|
||||||
authIssuer,
|
authIssuer,
|
||||||
authAudience,
|
authAudience,
|
||||||
@@ -21,12 +23,15 @@ func APIHandler(accountManager s.AccountManager, authIssuer string, authAudience
|
|||||||
|
|
||||||
corsMiddleware := cors.AllowAll()
|
corsMiddleware := cors.AllowAll()
|
||||||
|
|
||||||
acMiddleware := middleware.NewAccessControll(
|
acMiddleware := middleware.NewAccessControl(
|
||||||
authAudience,
|
authAudience,
|
||||||
accountManager.IsUserAdmin)
|
accountManager.IsUserAdmin)
|
||||||
|
|
||||||
apiHandler := mux.NewRouter()
|
rootRouter := mux.NewRouter()
|
||||||
apiHandler.Use(corsMiddleware.Handler, jwtMiddleware.Handler, acMiddleware.Handler)
|
metricsMiddleware := appMetrics.HTTPMiddleware()
|
||||||
|
|
||||||
|
apiHandler := rootRouter.PathPrefix("/api").Subrouter()
|
||||||
|
apiHandler.Use(metricsMiddleware.Handler, corsMiddleware.Handler, jwtMiddleware.Handler, acMiddleware.Handler)
|
||||||
|
|
||||||
groupsHandler := NewGroups(accountManager, authAudience)
|
groupsHandler := NewGroups(accountManager, authAudience)
|
||||||
rulesHandler := NewRules(accountManager, authAudience)
|
rulesHandler := NewRules(accountManager, authAudience)
|
||||||
@@ -34,39 +39,69 @@ func APIHandler(accountManager s.AccountManager, authIssuer string, authAudience
|
|||||||
keysHandler := NewSetupKeysHandler(accountManager, authAudience)
|
keysHandler := NewSetupKeysHandler(accountManager, authAudience)
|
||||||
userHandler := NewUserHandler(accountManager, authAudience)
|
userHandler := NewUserHandler(accountManager, authAudience)
|
||||||
routesHandler := NewRoutes(accountManager, authAudience)
|
routesHandler := NewRoutes(accountManager, authAudience)
|
||||||
|
nameserversHandler := NewNameservers(accountManager, authAudience)
|
||||||
|
|
||||||
apiHandler.HandleFunc("/api/peers", peersHandler.GetPeers).Methods("GET", "OPTIONS")
|
apiHandler.HandleFunc("/peers", peersHandler.GetPeers).Methods("GET", "OPTIONS")
|
||||||
apiHandler.HandleFunc("/api/peers/{id}", peersHandler.HandlePeer).
|
apiHandler.HandleFunc("/peers/{id}", peersHandler.HandlePeer).
|
||||||
Methods("GET", "PUT", "DELETE", "OPTIONS")
|
Methods("GET", "PUT", "DELETE", "OPTIONS")
|
||||||
apiHandler.HandleFunc("/api/users", userHandler.GetUsers).Methods("GET", "OPTIONS")
|
apiHandler.HandleFunc("/users", userHandler.GetUsers).Methods("GET", "OPTIONS")
|
||||||
apiHandler.HandleFunc("/api/setup-keys", keysHandler.GetKeys).Methods("GET", "POST", "OPTIONS")
|
apiHandler.HandleFunc("/users/{id}", userHandler.UpdateUser).Methods("PUT", "OPTIONS")
|
||||||
apiHandler.HandleFunc("/api/setup-keys/{id}", keysHandler.HandleKey).Methods("GET", "PUT", "OPTIONS")
|
apiHandler.HandleFunc("/users", userHandler.CreateUserHandler).Methods("POST", "OPTIONS")
|
||||||
|
|
||||||
apiHandler.HandleFunc("/api/setup-keys", keysHandler.GetKeys).Methods("POST", "OPTIONS")
|
apiHandler.HandleFunc("/setup-keys", keysHandler.GetAllSetupKeysHandler).Methods("GET", "OPTIONS")
|
||||||
apiHandler.HandleFunc("/api/setup-keys/{id}", keysHandler.HandleKey).
|
apiHandler.HandleFunc("/setup-keys", keysHandler.CreateSetupKeyHandler).Methods("POST", "OPTIONS")
|
||||||
Methods("GET", "PUT", "DELETE", "OPTIONS")
|
apiHandler.HandleFunc("/setup-keys/{id}", keysHandler.GetSetupKeyHandler).Methods("GET", "OPTIONS")
|
||||||
|
apiHandler.HandleFunc("/setup-keys/{id}", keysHandler.UpdateSetupKeyHandler).Methods("PUT", "OPTIONS")
|
||||||
|
|
||||||
apiHandler.HandleFunc("/api/rules", rulesHandler.GetAllRulesHandler).Methods("GET", "OPTIONS")
|
apiHandler.HandleFunc("/rules", rulesHandler.GetAllRulesHandler).Methods("GET", "OPTIONS")
|
||||||
apiHandler.HandleFunc("/api/rules", rulesHandler.CreateRuleHandler).Methods("POST", "OPTIONS")
|
apiHandler.HandleFunc("/rules", rulesHandler.CreateRuleHandler).Methods("POST", "OPTIONS")
|
||||||
apiHandler.HandleFunc("/api/rules/{id}", rulesHandler.UpdateRuleHandler).Methods("PUT", "OPTIONS")
|
apiHandler.HandleFunc("/rules/{id}", rulesHandler.UpdateRuleHandler).Methods("PUT", "OPTIONS")
|
||||||
apiHandler.HandleFunc("/api/rules/{id}", rulesHandler.PatchRuleHandler).Methods("PATCH", "OPTIONS")
|
apiHandler.HandleFunc("/rules/{id}", rulesHandler.PatchRuleHandler).Methods("PATCH", "OPTIONS")
|
||||||
apiHandler.HandleFunc("/api/rules/{id}", rulesHandler.GetRuleHandler).Methods("GET", "OPTIONS")
|
apiHandler.HandleFunc("/rules/{id}", rulesHandler.GetRuleHandler).Methods("GET", "OPTIONS")
|
||||||
apiHandler.HandleFunc("/api/rules/{id}", rulesHandler.DeleteRuleHandler).Methods("DELETE", "OPTIONS")
|
apiHandler.HandleFunc("/rules/{id}", rulesHandler.DeleteRuleHandler).Methods("DELETE", "OPTIONS")
|
||||||
|
|
||||||
apiHandler.HandleFunc("/api/groups", groupsHandler.GetAllGroupsHandler).Methods("GET", "OPTIONS")
|
apiHandler.HandleFunc("/groups", groupsHandler.GetAllGroupsHandler).Methods("GET", "OPTIONS")
|
||||||
apiHandler.HandleFunc("/api/groups", groupsHandler.CreateGroupHandler).Methods("POST", "OPTIONS")
|
apiHandler.HandleFunc("/groups", groupsHandler.CreateGroupHandler).Methods("POST", "OPTIONS")
|
||||||
apiHandler.HandleFunc("/api/groups/{id}", groupsHandler.UpdateGroupHandler).Methods("PUT", "OPTIONS")
|
apiHandler.HandleFunc("/groups/{id}", groupsHandler.UpdateGroupHandler).Methods("PUT", "OPTIONS")
|
||||||
apiHandler.HandleFunc("/api/groups/{id}", groupsHandler.PatchGroupHandler).Methods("PATCH", "OPTIONS")
|
apiHandler.HandleFunc("/groups/{id}", groupsHandler.PatchGroupHandler).Methods("PATCH", "OPTIONS")
|
||||||
apiHandler.HandleFunc("/api/groups/{id}", groupsHandler.GetGroupHandler).Methods("GET", "OPTIONS")
|
apiHandler.HandleFunc("/groups/{id}", groupsHandler.GetGroupHandler).Methods("GET", "OPTIONS")
|
||||||
apiHandler.HandleFunc("/api/groups/{id}", groupsHandler.DeleteGroupHandler).Methods("DELETE", "OPTIONS")
|
apiHandler.HandleFunc("/groups/{id}", groupsHandler.DeleteGroupHandler).Methods("DELETE", "OPTIONS")
|
||||||
|
|
||||||
apiHandler.HandleFunc("/api/routes", routesHandler.GetAllRoutesHandler).Methods("GET", "OPTIONS")
|
apiHandler.HandleFunc("/routes", routesHandler.GetAllRoutesHandler).Methods("GET", "OPTIONS")
|
||||||
apiHandler.HandleFunc("/api/routes", routesHandler.CreateRouteHandler).Methods("POST", "OPTIONS")
|
apiHandler.HandleFunc("/routes", routesHandler.CreateRouteHandler).Methods("POST", "OPTIONS")
|
||||||
apiHandler.HandleFunc("/api/routes/{id}", routesHandler.UpdateRouteHandler).Methods("PUT", "OPTIONS")
|
apiHandler.HandleFunc("/routes/{id}", routesHandler.UpdateRouteHandler).Methods("PUT", "OPTIONS")
|
||||||
apiHandler.HandleFunc("/api/routes/{id}", routesHandler.PatchRouteHandler).Methods("PATCH", "OPTIONS")
|
apiHandler.HandleFunc("/routes/{id}", routesHandler.PatchRouteHandler).Methods("PATCH", "OPTIONS")
|
||||||
apiHandler.HandleFunc("/api/routes/{id}", routesHandler.GetRouteHandler).Methods("GET", "OPTIONS")
|
apiHandler.HandleFunc("/routes/{id}", routesHandler.GetRouteHandler).Methods("GET", "OPTIONS")
|
||||||
apiHandler.HandleFunc("/api/routes/{id}", routesHandler.DeleteRouteHandler).Methods("DELETE", "OPTIONS")
|
apiHandler.HandleFunc("/routes/{id}", routesHandler.DeleteRouteHandler).Methods("DELETE", "OPTIONS")
|
||||||
|
|
||||||
return apiHandler, nil
|
apiHandler.HandleFunc("/dns/nameservers", nameserversHandler.GetAllNameserversHandler).Methods("GET", "OPTIONS")
|
||||||
|
apiHandler.HandleFunc("/dns/nameservers", nameserversHandler.CreateNameserverGroupHandler).Methods("POST", "OPTIONS")
|
||||||
|
apiHandler.HandleFunc("/dns/nameservers/{id}", nameserversHandler.UpdateNameserverGroupHandler).Methods("PUT", "OPTIONS")
|
||||||
|
apiHandler.HandleFunc("/dns/nameservers/{id}", nameserversHandler.PatchNameserverGroupHandler).Methods("PATCH", "OPTIONS")
|
||||||
|
apiHandler.HandleFunc("/dns/nameservers/{id}", nameserversHandler.GetNameserverGroupHandler).Methods("GET", "OPTIONS")
|
||||||
|
apiHandler.HandleFunc("/dns/nameservers/{id}", nameserversHandler.DeleteNameserverGroupHandler).Methods("DELETE", "OPTIONS")
|
||||||
|
|
||||||
|
err = apiHandler.Walk(func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error {
|
||||||
|
methods, err := route.GetMethods()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, method := range methods {
|
||||||
|
template, err := route.GetPathTemplate()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = metricsMiddleware.AddHTTPRequestResponseCounter(template, method)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return rootRouter, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
package middleware
|
package middleware
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"github.com/netbirdio/netbird/management/server/http/util"
|
||||||
|
"github.com/netbirdio/netbird/management/server/status"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/management/server/jwtclaims"
|
"github.com/netbirdio/netbird/management/server/jwtclaims"
|
||||||
@@ -9,38 +10,39 @@ import (
|
|||||||
|
|
||||||
type IsUserAdminFunc func(claims jwtclaims.AuthorizationClaims) (bool, error)
|
type IsUserAdminFunc func(claims jwtclaims.AuthorizationClaims) (bool, error)
|
||||||
|
|
||||||
// AccessControll middleware to restrict to make POST/PUT/DELETE requests by admin only
|
// AccessControl middleware to restrict to make POST/PUT/DELETE requests by admin only
|
||||||
type AccessControll struct {
|
type AccessControl struct {
|
||||||
jwtExtractor jwtclaims.ClaimsExtractor
|
jwtExtractor jwtclaims.ClaimsExtractor
|
||||||
isUserAdmin IsUserAdminFunc
|
isUserAdmin IsUserAdminFunc
|
||||||
audience string
|
audience string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAccessControll instance constructor
|
// NewAccessControl instance constructor
|
||||||
func NewAccessControll(audience string, isUserAdmin IsUserAdminFunc) *AccessControll {
|
func NewAccessControl(audience string, isUserAdmin IsUserAdminFunc) *AccessControl {
|
||||||
return &AccessControll{
|
return &AccessControl{
|
||||||
isUserAdmin: isUserAdmin,
|
isUserAdmin: isUserAdmin,
|
||||||
audience: audience,
|
audience: audience,
|
||||||
jwtExtractor: *jwtclaims.NewClaimsExtractor(nil),
|
jwtExtractor: *jwtclaims.NewClaimsExtractor(nil),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handler method of the middleware which forbinneds all modify requests for non admin users
|
// Handler method of the middleware which forbids all modify requests for non admin users
|
||||||
func (a *AccessControll) Handler(h http.Handler) http.Handler {
|
// It also adds
|
||||||
|
func (a *AccessControl) Handler(h http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
jwtClaims := a.jwtExtractor.ExtractClaimsFromRequestContext(r, a.audience)
|
jwtClaims := a.jwtExtractor.ExtractClaimsFromRequestContext(r, a.audience)
|
||||||
|
|
||||||
ok, err := a.isUserAdmin(jwtClaims)
|
ok, err := a.isUserAdmin(jwtClaims)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, fmt.Sprintf("error get user from JWT: %v", err), http.StatusUnauthorized)
|
util.WriteError(status.Errorf(status.Unauthorized, "invalid JWT"), w)
|
||||||
return
|
return
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
switch r.Method {
|
switch r.Method {
|
||||||
|
|
||||||
case http.MethodDelete, http.MethodPost, http.MethodPatch, http.MethodPut:
|
case http.MethodDelete, http.MethodPost, http.MethodPatch, http.MethodPut:
|
||||||
http.Error(w, "user is not admin", http.StatusForbidden)
|
util.WriteError(status.Errorf(status.PermissionDenied, "only admin can perform this operation"), w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,12 +7,12 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
//Jwks is a collection of JSONWebKeys obtained from Config.HttpServerConfig.AuthKeysLocation
|
// Jwks is a collection of JSONWebKeys obtained from Config.HttpServerConfig.AuthKeysLocation
|
||||||
type Jwks struct {
|
type Jwks struct {
|
||||||
Keys []JSONWebKeys `json:"keys"`
|
Keys []JSONWebKeys `json:"keys"`
|
||||||
}
|
}
|
||||||
|
|
||||||
//JSONWebKeys is a representation of a Jason Web Key
|
// JSONWebKeys is a representation of a Jason Web Key
|
||||||
type JSONWebKeys struct {
|
type JSONWebKeys struct {
|
||||||
Kty string `json:"kty"`
|
Kty string `json:"kty"`
|
||||||
Kid string `json:"kid"`
|
Kid string `json:"kid"`
|
||||||
@@ -22,7 +22,7 @@ type JSONWebKeys struct {
|
|||||||
X5c []string `json:"x5c"`
|
X5c []string `json:"x5c"`
|
||||||
}
|
}
|
||||||
|
|
||||||
//NewJwtMiddleware creates new middleware to verify the JWT token sent via Authorization header
|
// NewJwtMiddleware creates new middleware to verify the JWT token sent via Authorization header
|
||||||
func NewJwtMiddleware(issuer string, audience string, keysLocation string) (*JWTMiddleware, error) {
|
func NewJwtMiddleware(issuer string, audience string, keysLocation string) (*JWTMiddleware, error) {
|
||||||
|
|
||||||
keys, err := getPemKeys(keysLocation)
|
keys, err := getPemKeys(keysLocation)
|
||||||
@@ -66,7 +66,6 @@ func getPemKeys(keysLocation string) (*Jwks, error) {
|
|||||||
|
|
||||||
var jwks = &Jwks{}
|
var jwks = &Jwks{}
|
||||||
err = json.NewDecoder(resp.Body).Decode(jwks)
|
err = json.NewDecoder(resp.Body).Decode(jwks)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return jwks, err
|
return jwks, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/golang-jwt/jwt"
|
"github.com/golang-jwt/jwt"
|
||||||
|
"github.com/netbirdio/netbird/management/server/http/util"
|
||||||
|
"github.com/netbirdio/netbird/management/server/status"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -57,7 +59,7 @@ type JWTMiddleware struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func OnError(w http.ResponseWriter, r *http.Request, err string) {
|
func OnError(w http.ResponseWriter, r *http.Request, err string) {
|
||||||
http.Error(w, err, http.StatusUnauthorized)
|
util.WriteError(status.Errorf(status.Unauthorized, ""), w)
|
||||||
}
|
}
|
||||||
|
|
||||||
// New constructs a new Secure instance with supplied options.
|
// New constructs a new Secure instance with supplied options.
|
||||||
@@ -186,7 +188,7 @@ func (m *JWTMiddleware) CheckJWTFromRequest(w http.ResponseWriter, r *http.Reque
|
|||||||
|
|
||||||
validatedToken, err := m.ValidateAndParse(token)
|
validatedToken, err := m.ValidateAndParse(token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
m.Options.ErrorHandler(w, r, "The token isn't valid")
|
m.Options.ErrorHandler(w, r, err.Error())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
308
management/server/http/nameservers.go
Normal file
308
management/server/http/nameservers.go
Normal file
@@ -0,0 +1,308 @@
|
|||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
nbdns "github.com/netbirdio/netbird/dns"
|
||||||
|
"github.com/netbirdio/netbird/management/server"
|
||||||
|
"github.com/netbirdio/netbird/management/server/http/api"
|
||||||
|
"github.com/netbirdio/netbird/management/server/http/util"
|
||||||
|
"github.com/netbirdio/netbird/management/server/jwtclaims"
|
||||||
|
"github.com/netbirdio/netbird/management/server/status"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Nameservers is the nameserver group handler of the account
|
||||||
|
type Nameservers struct {
|
||||||
|
jwtExtractor jwtclaims.ClaimsExtractor
|
||||||
|
accountManager server.AccountManager
|
||||||
|
authAudience string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewNameservers returns a new instance of Nameservers handler
|
||||||
|
func NewNameservers(accountManager server.AccountManager, authAudience string) *Nameservers {
|
||||||
|
return &Nameservers{
|
||||||
|
accountManager: accountManager,
|
||||||
|
authAudience: authAudience,
|
||||||
|
jwtExtractor: *jwtclaims.NewClaimsExtractor(nil),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAllNameserversHandler returns the list of nameserver groups for the account
|
||||||
|
func (h *Nameservers) GetAllNameserversHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
claims := h.jwtExtractor.ExtractClaimsFromRequestContext(r, h.authAudience)
|
||||||
|
account, _, err := h.accountManager.GetAccountFromToken(claims)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
nsGroups, err := h.accountManager.ListNameServerGroups(account.Id)
|
||||||
|
if err != nil {
|
||||||
|
util.WriteError(err, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
apiNameservers := make([]*api.NameserverGroup, 0)
|
||||||
|
for _, r := range nsGroups {
|
||||||
|
apiNameservers = append(apiNameservers, toNameserverGroupResponse(r))
|
||||||
|
}
|
||||||
|
|
||||||
|
util.WriteJSONObject(w, apiNameservers)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateNameserverGroupHandler handles nameserver group creation request
|
||||||
|
func (h *Nameservers) CreateNameserverGroupHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
claims := h.jwtExtractor.ExtractClaimsFromRequestContext(r, h.authAudience)
|
||||||
|
account, _, err := h.accountManager.GetAccountFromToken(claims)
|
||||||
|
if err != nil {
|
||||||
|
util.WriteError(err, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var req api.PostApiDnsNameserversJSONRequestBody
|
||||||
|
err = json.NewDecoder(r.Body).Decode(&req)
|
||||||
|
if err != nil {
|
||||||
|
util.WriteErrorResponse("couldn't parse JSON request", http.StatusBadRequest, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
nsList, err := toServerNSList(req.Nameservers)
|
||||||
|
if err != nil {
|
||||||
|
util.WriteError(status.Errorf(status.InvalidArgument, "invalid NS servers format"), w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
nsGroup, err := h.accountManager.CreateNameServerGroup(account.Id, req.Name, req.Description, nsList, req.Groups, req.Primary, req.Domains, req.Enabled)
|
||||||
|
if err != nil {
|
||||||
|
util.WriteError(err, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := toNameserverGroupResponse(nsGroup)
|
||||||
|
|
||||||
|
util.WriteJSONObject(w, &resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateNameserverGroupHandler handles update to a nameserver group identified by a given ID
|
||||||
|
func (h *Nameservers) UpdateNameserverGroupHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
claims := h.jwtExtractor.ExtractClaimsFromRequestContext(r, h.authAudience)
|
||||||
|
account, _, err := h.accountManager.GetAccountFromToken(claims)
|
||||||
|
if err != nil {
|
||||||
|
util.WriteError(err, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
nsGroupID := mux.Vars(r)["id"]
|
||||||
|
if len(nsGroupID) == 0 {
|
||||||
|
util.WriteError(status.Errorf(status.InvalidArgument, "invalid nameserver group ID"), w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var req api.PutApiDnsNameserversIdJSONRequestBody
|
||||||
|
err = json.NewDecoder(r.Body).Decode(&req)
|
||||||
|
if err != nil {
|
||||||
|
util.WriteErrorResponse("couldn't parse JSON request", http.StatusBadRequest, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
nsList, err := toServerNSList(req.Nameservers)
|
||||||
|
if err != nil {
|
||||||
|
util.WriteError(status.Errorf(status.InvalidArgument, "invalid NS servers format"), w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedNSGroup := &nbdns.NameServerGroup{
|
||||||
|
ID: nsGroupID,
|
||||||
|
Name: req.Name,
|
||||||
|
Description: req.Description,
|
||||||
|
Primary: req.Primary,
|
||||||
|
Domains: req.Domains,
|
||||||
|
NameServers: nsList,
|
||||||
|
Groups: req.Groups,
|
||||||
|
Enabled: req.Enabled,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = h.accountManager.SaveNameServerGroup(account.Id, updatedNSGroup)
|
||||||
|
if err != nil {
|
||||||
|
util.WriteError(err, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := toNameserverGroupResponse(updatedNSGroup)
|
||||||
|
|
||||||
|
util.WriteJSONObject(w, &resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PatchNameserverGroupHandler handles patch updates to a nameserver group identified by a given ID
|
||||||
|
func (h *Nameservers) PatchNameserverGroupHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
claims := h.jwtExtractor.ExtractClaimsFromRequestContext(r, h.authAudience)
|
||||||
|
account, _, err := h.accountManager.GetAccountFromToken(claims)
|
||||||
|
if err != nil {
|
||||||
|
util.WriteError(err, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
nsGroupID := mux.Vars(r)["id"]
|
||||||
|
if len(nsGroupID) == 0 {
|
||||||
|
util.WriteError(status.Errorf(status.InvalidArgument, "invalid nameserver group ID"), w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var req api.PatchApiDnsNameserversIdJSONRequestBody
|
||||||
|
err = json.NewDecoder(r.Body).Decode(&req)
|
||||||
|
if err != nil {
|
||||||
|
util.WriteErrorResponse("couldn't parse JSON request", http.StatusBadRequest, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var operations []server.NameServerGroupUpdateOperation
|
||||||
|
for _, patch := range req {
|
||||||
|
if patch.Op != api.NameserverGroupPatchOperationOpReplace {
|
||||||
|
util.WriteError(status.Errorf(status.InvalidArgument,
|
||||||
|
"nameserver groups only accepts replace operations, got %s", patch.Op), w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch patch.Path {
|
||||||
|
case api.NameserverGroupPatchOperationPathName:
|
||||||
|
operations = append(operations, server.NameServerGroupUpdateOperation{
|
||||||
|
Type: server.UpdateNameServerGroupName,
|
||||||
|
Values: patch.Value,
|
||||||
|
})
|
||||||
|
case api.NameserverGroupPatchOperationPathDescription:
|
||||||
|
operations = append(operations, server.NameServerGroupUpdateOperation{
|
||||||
|
Type: server.UpdateNameServerGroupDescription,
|
||||||
|
Values: patch.Value,
|
||||||
|
})
|
||||||
|
case api.NameserverGroupPatchOperationPathPrimary:
|
||||||
|
operations = append(operations, server.NameServerGroupUpdateOperation{
|
||||||
|
Type: server.UpdateNameServerGroupPrimary,
|
||||||
|
Values: patch.Value,
|
||||||
|
})
|
||||||
|
case api.NameserverGroupPatchOperationPathDomains:
|
||||||
|
operations = append(operations, server.NameServerGroupUpdateOperation{
|
||||||
|
Type: server.UpdateNameServerGroupDomains,
|
||||||
|
Values: patch.Value,
|
||||||
|
})
|
||||||
|
case api.NameserverGroupPatchOperationPathNameservers:
|
||||||
|
operations = append(operations, server.NameServerGroupUpdateOperation{
|
||||||
|
Type: server.UpdateNameServerGroupNameServers,
|
||||||
|
Values: patch.Value,
|
||||||
|
})
|
||||||
|
case api.NameserverGroupPatchOperationPathGroups:
|
||||||
|
operations = append(operations, server.NameServerGroupUpdateOperation{
|
||||||
|
Type: server.UpdateNameServerGroupGroups,
|
||||||
|
Values: patch.Value,
|
||||||
|
})
|
||||||
|
case api.NameserverGroupPatchOperationPathEnabled:
|
||||||
|
operations = append(operations, server.NameServerGroupUpdateOperation{
|
||||||
|
Type: server.UpdateNameServerGroupEnabled,
|
||||||
|
Values: patch.Value,
|
||||||
|
})
|
||||||
|
default:
|
||||||
|
util.WriteError(status.Errorf(status.InvalidArgument, "invalid patch path"), w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedNSGroup, err := h.accountManager.UpdateNameServerGroup(account.Id, nsGroupID, operations)
|
||||||
|
if err != nil {
|
||||||
|
util.WriteError(err, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := toNameserverGroupResponse(updatedNSGroup)
|
||||||
|
|
||||||
|
util.WriteJSONObject(w, &resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteNameserverGroupHandler handles nameserver group deletion request
|
||||||
|
func (h *Nameservers) DeleteNameserverGroupHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
claims := h.jwtExtractor.ExtractClaimsFromRequestContext(r, h.authAudience)
|
||||||
|
account, _, err := h.accountManager.GetAccountFromToken(claims)
|
||||||
|
if err != nil {
|
||||||
|
util.WriteError(err, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
nsGroupID := mux.Vars(r)["id"]
|
||||||
|
if len(nsGroupID) == 0 {
|
||||||
|
util.WriteError(status.Errorf(status.InvalidArgument, "invalid nameserver group ID"), w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = h.accountManager.DeleteNameServerGroup(account.Id, nsGroupID)
|
||||||
|
if err != nil {
|
||||||
|
util.WriteError(err, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
util.WriteJSONObject(w, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNameserverGroupHandler handles a nameserver group Get request identified by ID
|
||||||
|
func (h *Nameservers) GetNameserverGroupHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
claims := h.jwtExtractor.ExtractClaimsFromRequestContext(r, h.authAudience)
|
||||||
|
account, _, err := h.accountManager.GetAccountFromToken(claims)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
nsGroupID := mux.Vars(r)["id"]
|
||||||
|
if len(nsGroupID) == 0 {
|
||||||
|
util.WriteError(status.Errorf(status.InvalidArgument, "invalid nameserver group ID"), w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
nsGroup, err := h.accountManager.GetNameServerGroup(account.Id, nsGroupID)
|
||||||
|
if err != nil {
|
||||||
|
util.WriteError(err, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := toNameserverGroupResponse(nsGroup)
|
||||||
|
|
||||||
|
util.WriteJSONObject(w, &resp)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func toServerNSList(apiNSList []api.Nameserver) ([]nbdns.NameServer, error) {
|
||||||
|
var nsList []nbdns.NameServer
|
||||||
|
for _, apiNS := range apiNSList {
|
||||||
|
parsed, err := nbdns.ParseNameServerURL(fmt.Sprintf("%s://%s:%d", apiNS.NsType, apiNS.Ip, apiNS.Port))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
nsList = append(nsList, parsed)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nsList, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func toNameserverGroupResponse(serverNSGroup *nbdns.NameServerGroup) *api.NameserverGroup {
|
||||||
|
var nsList []api.Nameserver
|
||||||
|
for _, ns := range serverNSGroup.NameServers {
|
||||||
|
apiNS := api.Nameserver{
|
||||||
|
Ip: ns.IP.String(),
|
||||||
|
NsType: api.NameserverNsType(ns.NSType.String()),
|
||||||
|
Port: ns.Port,
|
||||||
|
}
|
||||||
|
nsList = append(nsList, apiNS)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &api.NameserverGroup{
|
||||||
|
Id: serverNSGroup.ID,
|
||||||
|
Name: serverNSGroup.Name,
|
||||||
|
Description: serverNSGroup.Description,
|
||||||
|
Primary: serverNSGroup.Primary,
|
||||||
|
Domains: serverNSGroup.Domains,
|
||||||
|
Groups: serverNSGroup.Groups,
|
||||||
|
Nameservers: nsList,
|
||||||
|
Enabled: serverNSGroup.Enabled,
|
||||||
|
}
|
||||||
|
}
|
||||||
295
management/server/http/nameservers_test.go
Normal file
295
management/server/http/nameservers_test.go
Normal file
@@ -0,0 +1,295 @@
|
|||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
nbdns "github.com/netbirdio/netbird/dns"
|
||||||
|
"github.com/netbirdio/netbird/management/server/http/api"
|
||||||
|
"github.com/netbirdio/netbird/management/server/status"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"net/netip"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/netbirdio/netbird/management/server"
|
||||||
|
"github.com/netbirdio/netbird/management/server/jwtclaims"
|
||||||
|
"github.com/netbirdio/netbird/management/server/mock_server"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
existingNSGroupID = "existingNSGroupID"
|
||||||
|
notFoundNSGroupID = "notFoundNSGroupID"
|
||||||
|
testNSGroupAccountID = "test_id"
|
||||||
|
)
|
||||||
|
|
||||||
|
var testingNSAccount = &server.Account{
|
||||||
|
Id: testNSGroupAccountID,
|
||||||
|
Domain: "hotmail.com",
|
||||||
|
Users: map[string]*server.User{
|
||||||
|
"test_user": server.NewAdminUser("test_user"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var baseExistingNSGroup = &nbdns.NameServerGroup{
|
||||||
|
ID: existingNSGroupID,
|
||||||
|
Name: "super",
|
||||||
|
Description: "super",
|
||||||
|
Primary: true,
|
||||||
|
NameServers: []nbdns.NameServer{
|
||||||
|
{
|
||||||
|
IP: netip.MustParseAddr("1.1.1.1"),
|
||||||
|
NSType: nbdns.UDPNameServerType,
|
||||||
|
Port: nbdns.DefaultDNSPort,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
IP: netip.MustParseAddr("1.1.2.2"),
|
||||||
|
NSType: nbdns.UDPNameServerType,
|
||||||
|
Port: nbdns.DefaultDNSPort,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Groups: []string{"testing"},
|
||||||
|
Enabled: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
func initNameserversTestData() *Nameservers {
|
||||||
|
return &Nameservers{
|
||||||
|
accountManager: &mock_server.MockAccountManager{
|
||||||
|
GetNameServerGroupFunc: func(accountID, nsGroupID string) (*nbdns.NameServerGroup, error) {
|
||||||
|
if nsGroupID == existingNSGroupID {
|
||||||
|
return baseExistingNSGroup.Copy(), nil
|
||||||
|
}
|
||||||
|
return nil, status.Errorf(status.NotFound, "nameserver group with ID %s not found", nsGroupID)
|
||||||
|
},
|
||||||
|
CreateNameServerGroupFunc: func(accountID string, name, description string, nameServerList []nbdns.NameServer, groups []string, primary bool, domains []string, enabled bool) (*nbdns.NameServerGroup, error) {
|
||||||
|
return &nbdns.NameServerGroup{
|
||||||
|
ID: existingNSGroupID,
|
||||||
|
Name: name,
|
||||||
|
Description: description,
|
||||||
|
NameServers: nameServerList,
|
||||||
|
Groups: groups,
|
||||||
|
Enabled: enabled,
|
||||||
|
Primary: primary,
|
||||||
|
Domains: domains,
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
DeleteNameServerGroupFunc: func(accountID, nsGroupID string) error {
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
SaveNameServerGroupFunc: func(accountID string, nsGroupToSave *nbdns.NameServerGroup) error {
|
||||||
|
if nsGroupToSave.ID == existingNSGroupID {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return status.Errorf(status.NotFound, "nameserver group with ID %s was not found", nsGroupToSave.ID)
|
||||||
|
},
|
||||||
|
UpdateNameServerGroupFunc: func(accountID, nsGroupID string, operations []server.NameServerGroupUpdateOperation) (*nbdns.NameServerGroup, error) {
|
||||||
|
nsGroupToUpdate := baseExistingNSGroup.Copy()
|
||||||
|
if nsGroupID != nsGroupToUpdate.ID {
|
||||||
|
return nil, status.Errorf(status.NotFound, "nameserver group ID %s no longer exists", nsGroupID)
|
||||||
|
}
|
||||||
|
for _, operation := range operations {
|
||||||
|
switch operation.Type {
|
||||||
|
case server.UpdateNameServerGroupName:
|
||||||
|
nsGroupToUpdate.Name = operation.Values[0]
|
||||||
|
case server.UpdateNameServerGroupDescription:
|
||||||
|
nsGroupToUpdate.Description = operation.Values[0]
|
||||||
|
case server.UpdateNameServerGroupNameServers:
|
||||||
|
var parsedNSList []nbdns.NameServer
|
||||||
|
for _, nsURL := range operation.Values {
|
||||||
|
parsed, err := nbdns.ParseNameServerURL(nsURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
parsedNSList = append(parsedNSList, parsed)
|
||||||
|
}
|
||||||
|
nsGroupToUpdate.NameServers = parsedNSList
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nsGroupToUpdate, nil
|
||||||
|
},
|
||||||
|
GetAccountFromTokenFunc: func(_ jwtclaims.AuthorizationClaims) (*server.Account, *server.User, error) {
|
||||||
|
return testingNSAccount, nil, nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
authAudience: "",
|
||||||
|
jwtExtractor: jwtclaims.ClaimsExtractor{
|
||||||
|
ExtractClaimsFromRequestContext: func(r *http.Request, authAudiance string) jwtclaims.AuthorizationClaims {
|
||||||
|
return jwtclaims.AuthorizationClaims{
|
||||||
|
UserId: "test_user",
|
||||||
|
Domain: "hotmail.com",
|
||||||
|
AccountId: testNSGroupAccountID,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNameserversHandlers(t *testing.T) {
|
||||||
|
tt := []struct {
|
||||||
|
name string
|
||||||
|
expectedStatus int
|
||||||
|
expectedBody bool
|
||||||
|
expectedNSGroup *api.NameserverGroup
|
||||||
|
requestType string
|
||||||
|
requestPath string
|
||||||
|
requestBody io.Reader
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Get Existing Nameserver Group",
|
||||||
|
requestType: http.MethodGet,
|
||||||
|
requestPath: "/api/dns/nameservers/" + existingNSGroupID,
|
||||||
|
expectedStatus: http.StatusOK,
|
||||||
|
expectedBody: true,
|
||||||
|
expectedNSGroup: toNameserverGroupResponse(baseExistingNSGroup),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Get Not Existing Nameserver Group",
|
||||||
|
requestType: http.MethodGet,
|
||||||
|
requestPath: "/api/dns/nameservers/" + notFoundNSGroupID,
|
||||||
|
expectedStatus: http.StatusNotFound,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "POST OK",
|
||||||
|
requestType: http.MethodPost,
|
||||||
|
requestPath: "/api/dns/nameservers",
|
||||||
|
requestBody: bytes.NewBuffer(
|
||||||
|
[]byte("{\"name\":\"name\",\"Description\":\"Post\",\"nameservers\":[{\"ip\":\"1.1.1.1\",\"ns_type\":\"udp\",\"port\":53}],\"groups\":[\"group\"],\"enabled\":true,\"primary\":true}")),
|
||||||
|
expectedStatus: http.StatusOK,
|
||||||
|
expectedBody: true,
|
||||||
|
expectedNSGroup: &api.NameserverGroup{
|
||||||
|
Id: existingNSGroupID,
|
||||||
|
Name: "name",
|
||||||
|
Description: "Post",
|
||||||
|
Nameservers: []api.Nameserver{
|
||||||
|
{
|
||||||
|
Ip: "1.1.1.1",
|
||||||
|
NsType: "udp",
|
||||||
|
Port: 53,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Groups: []string{"group"},
|
||||||
|
Enabled: true,
|
||||||
|
Primary: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "POST Invalid Nameserver",
|
||||||
|
requestType: http.MethodPost,
|
||||||
|
requestPath: "/api/dns/nameservers",
|
||||||
|
requestBody: bytes.NewBuffer(
|
||||||
|
[]byte("{\"name\":\"name\",\"Description\":\"Post\",\"nameservers\":[{\"ip\":\"1000\",\"ns_type\":\"udp\",\"port\":53}],\"groups\":[\"group\"],\"enabled\":true,\"primary\":true}")),
|
||||||
|
expectedStatus: http.StatusUnprocessableEntity,
|
||||||
|
expectedBody: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "PUT OK",
|
||||||
|
requestType: http.MethodPut,
|
||||||
|
requestPath: "/api/dns/nameservers/" + existingNSGroupID,
|
||||||
|
requestBody: bytes.NewBuffer(
|
||||||
|
[]byte("{\"name\":\"name\",\"Description\":\"Post\",\"nameservers\":[{\"ip\":\"1.1.1.1\",\"ns_type\":\"udp\",\"port\":53}],\"groups\":[\"group\"],\"enabled\":true,\"primary\":true}")),
|
||||||
|
expectedStatus: http.StatusOK,
|
||||||
|
expectedBody: true,
|
||||||
|
expectedNSGroup: &api.NameserverGroup{
|
||||||
|
Id: existingNSGroupID,
|
||||||
|
Name: "name",
|
||||||
|
Description: "Post",
|
||||||
|
Nameservers: []api.Nameserver{
|
||||||
|
{
|
||||||
|
Ip: "1.1.1.1",
|
||||||
|
NsType: "udp",
|
||||||
|
Port: 53,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Groups: []string{"group"},
|
||||||
|
Enabled: true,
|
||||||
|
Primary: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "PUT Not Existing Nameserver Group",
|
||||||
|
requestType: http.MethodPut,
|
||||||
|
requestPath: "/api/dns/nameservers/" + notFoundNSGroupID,
|
||||||
|
requestBody: bytes.NewBuffer(
|
||||||
|
[]byte("{\"name\":\"name\",\"Description\":\"Post\",\"nameservers\":[{\"ip\":\"1.1.1.1\",\"ns_type\":\"udp\",\"port\":53}],\"groups\":[\"group\"],\"enabled\":true,\"primary\":true}")),
|
||||||
|
expectedStatus: http.StatusNotFound,
|
||||||
|
expectedBody: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "PUT Invalid Nameserver",
|
||||||
|
requestType: http.MethodPut,
|
||||||
|
requestPath: "/api/dns/nameservers/" + notFoundNSGroupID,
|
||||||
|
requestBody: bytes.NewBuffer(
|
||||||
|
[]byte("{\"name\":\"name\",\"Description\":\"Post\",\"nameservers\":[{\"ip\":\"100\",\"ns_type\":\"udp\",\"port\":53}],\"groups\":[\"group\"],\"enabled\":true,\"primary\":true}")),
|
||||||
|
expectedStatus: http.StatusUnprocessableEntity,
|
||||||
|
expectedBody: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "PATCH OK",
|
||||||
|
requestType: http.MethodPatch,
|
||||||
|
requestPath: "/api/dns/nameservers/" + existingNSGroupID,
|
||||||
|
requestBody: bytes.NewBufferString("[{\"op\":\"replace\",\"path\":\"description\",\"value\":[\"NewDesc\"]}]"),
|
||||||
|
expectedStatus: http.StatusOK,
|
||||||
|
expectedBody: true,
|
||||||
|
expectedNSGroup: &api.NameserverGroup{
|
||||||
|
Id: existingNSGroupID,
|
||||||
|
Name: baseExistingNSGroup.Name,
|
||||||
|
Description: "NewDesc",
|
||||||
|
Nameservers: toNameserverGroupResponse(baseExistingNSGroup).Nameservers,
|
||||||
|
Groups: baseExistingNSGroup.Groups,
|
||||||
|
Enabled: baseExistingNSGroup.Enabled,
|
||||||
|
Primary: baseExistingNSGroup.Primary,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "PATCH Invalid Nameserver Group OK",
|
||||||
|
requestType: http.MethodPatch,
|
||||||
|
requestPath: "/api/dns/nameservers/" + notFoundRouteID,
|
||||||
|
requestBody: bytes.NewBufferString("[{\"op\":\"replace\",\"path\":\"description\",\"value\":[\"NewDesc\"]}]"),
|
||||||
|
expectedStatus: http.StatusNotFound,
|
||||||
|
expectedBody: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
p := initNameserversTestData()
|
||||||
|
|
||||||
|
for _, tc := range tt {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
recorder := httptest.NewRecorder()
|
||||||
|
req := httptest.NewRequest(tc.requestType, tc.requestPath, tc.requestBody)
|
||||||
|
|
||||||
|
router := mux.NewRouter()
|
||||||
|
router.HandleFunc("/api/dns/nameservers/{id}", p.GetNameserverGroupHandler).Methods("GET")
|
||||||
|
router.HandleFunc("/api/dns/nameservers", p.CreateNameserverGroupHandler).Methods("POST")
|
||||||
|
router.HandleFunc("/api/dns/nameservers/{id}", p.DeleteNameserverGroupHandler).Methods("DELETE")
|
||||||
|
router.HandleFunc("/api/dns/nameservers/{id}", p.UpdateNameserverGroupHandler).Methods("PUT")
|
||||||
|
router.HandleFunc("/api/dns/nameservers/{id}", p.PatchNameserverGroupHandler).Methods("PATCH")
|
||||||
|
router.ServeHTTP(recorder, req)
|
||||||
|
|
||||||
|
res := recorder.Result()
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
content, err := io.ReadAll(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("I don't know what I expected; %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if status := recorder.Code; status != tc.expectedStatus {
|
||||||
|
t.Errorf("handler returned wrong status code: got %v want %v, content: %s",
|
||||||
|
status, tc.expectedStatus, string(content))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !tc.expectedBody {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
got := &api.NameserverGroup{}
|
||||||
|
if err = json.Unmarshal(content, &got); err != nil {
|
||||||
|
t.Fatalf("Sent content is not in correct json format; %v", err)
|
||||||
|
}
|
||||||
|
assert.Equal(t, tc.expectedNSGroup, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,12 +6,13 @@ import (
|
|||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/netbirdio/netbird/management/server"
|
"github.com/netbirdio/netbird/management/server"
|
||||||
"github.com/netbirdio/netbird/management/server/http/api"
|
"github.com/netbirdio/netbird/management/server/http/api"
|
||||||
|
"github.com/netbirdio/netbird/management/server/http/util"
|
||||||
"github.com/netbirdio/netbird/management/server/jwtclaims"
|
"github.com/netbirdio/netbird/management/server/jwtclaims"
|
||||||
log "github.com/sirupsen/logrus"
|
"github.com/netbirdio/netbird/management/server/status"
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
//Peers is a handler that returns peers of the account
|
// Peers is a handler that returns peers of the account
|
||||||
type Peers struct {
|
type Peers struct {
|
||||||
accountManager server.AccountManager
|
accountManager server.AccountManager
|
||||||
authAudience string
|
authAudience string
|
||||||
@@ -28,53 +29,53 @@ func NewPeers(accountManager server.AccountManager, authAudience string) *Peers
|
|||||||
|
|
||||||
func (h *Peers) updatePeer(account *server.Account, peer *server.Peer, w http.ResponseWriter, r *http.Request) {
|
func (h *Peers) updatePeer(account *server.Account, peer *server.Peer, w http.ResponseWriter, r *http.Request) {
|
||||||
req := &api.PutApiPeersIdJSONBody{}
|
req := &api.PutApiPeersIdJSONBody{}
|
||||||
peerIp := peer.IP
|
|
||||||
err := json.NewDecoder(r.Body).Decode(&req)
|
err := json.NewDecoder(r.Body).Decode(&req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
util.WriteErrorResponse("couldn't parse JSON request", http.StatusBadRequest, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
update := &server.Peer{Key: peer.Key, SSHEnabled: req.SshEnabled, Name: req.Name}
|
update := &server.Peer{Key: peer.Key, SSHEnabled: req.SshEnabled, Name: req.Name}
|
||||||
peer, err = h.accountManager.UpdatePeer(account.Id, update)
|
peer, err = h.accountManager.UpdatePeer(account.Id, update)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed updating peer %s under account %s %v", peerIp, account.Id, err)
|
util.WriteError(err, w)
|
||||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
writeJSONObject(w, toPeerResponse(peer, account))
|
dnsDomain := h.accountManager.GetDNSDomain()
|
||||||
|
util.WriteJSONObject(w, toPeerResponse(peer, account, dnsDomain))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Peers) deletePeer(accountId string, peer *server.Peer, w http.ResponseWriter, r *http.Request) {
|
func (h *Peers) deletePeer(accountId string, peer *server.Peer, w http.ResponseWriter, r *http.Request) {
|
||||||
_, err := h.accountManager.DeletePeer(accountId, peer.Key)
|
_, err := h.accountManager.DeletePeer(accountId, peer.Key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed deleteing peer %s, %v", peer.IP, err)
|
util.WriteError(err, w)
|
||||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
writeJSONObject(w, "")
|
util.WriteJSONObject(w, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Peers) HandlePeer(w http.ResponseWriter, r *http.Request) {
|
func (h *Peers) HandlePeer(w http.ResponseWriter, r *http.Request) {
|
||||||
account, err := getJWTAccount(h.accountManager, h.jwtExtractor, h.authAudience, r)
|
claims := h.jwtExtractor.ExtractClaimsFromRequestContext(r, h.authAudience)
|
||||||
|
account, _, err := h.accountManager.GetAccountFromToken(claims)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err)
|
util.WriteError(err, w)
|
||||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
peerId := vars["id"] //effectively peer IP address
|
peerId := vars["id"] //effectively peer IP address
|
||||||
if len(peerId) == 0 {
|
if len(peerId) == 0 {
|
||||||
http.Error(w, "invalid peer Id", http.StatusBadRequest)
|
util.WriteError(status.Errorf(status.InvalidArgument, "invalid peer ID"), w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
peer, err := h.accountManager.GetPeerByIP(account.Id, peerId)
|
peer, err := h.accountManager.GetPeerByIP(account.Id, peerId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "peer not found", http.StatusNotFound)
|
util.WriteError(err, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dnsDomain := h.accountManager.GetDNSDomain()
|
||||||
|
|
||||||
switch r.Method {
|
switch r.Method {
|
||||||
case http.MethodDelete:
|
case http.MethodDelete:
|
||||||
h.deletePeer(account.Id, peer, w, r)
|
h.deletePeer(account.Id, peer, w, r)
|
||||||
@@ -83,11 +84,11 @@ func (h *Peers) HandlePeer(w http.ResponseWriter, r *http.Request) {
|
|||||||
h.updatePeer(account, peer, w, r)
|
h.updatePeer(account, peer, w, r)
|
||||||
return
|
return
|
||||||
case http.MethodGet:
|
case http.MethodGet:
|
||||||
writeJSONObject(w, toPeerResponse(peer, account))
|
util.WriteJSONObject(w, toPeerResponse(peer, account, dnsDomain))
|
||||||
return
|
return
|
||||||
|
|
||||||
default:
|
default:
|
||||||
http.Error(w, "", http.StatusNotFound)
|
util.WriteError(status.Errorf(status.NotFound, "unknown METHOD"), w)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -95,25 +96,33 @@ func (h *Peers) HandlePeer(w http.ResponseWriter, r *http.Request) {
|
|||||||
func (h *Peers) GetPeers(w http.ResponseWriter, r *http.Request) {
|
func (h *Peers) GetPeers(w http.ResponseWriter, r *http.Request) {
|
||||||
switch r.Method {
|
switch r.Method {
|
||||||
case http.MethodGet:
|
case http.MethodGet:
|
||||||
account, err := getJWTAccount(h.accountManager, h.jwtExtractor, h.authAudience, r)
|
claims := h.jwtExtractor.ExtractClaimsFromRequestContext(r, h.authAudience)
|
||||||
|
account, user, err := h.accountManager.GetAccountFromToken(claims)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err)
|
util.WriteError(err, w)
|
||||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
respBody := []*api.Peer{}
|
peers, err := h.accountManager.GetPeers(account.Id, user.Id)
|
||||||
for _, peer := range account.Peers {
|
if err != nil {
|
||||||
respBody = append(respBody, toPeerResponse(peer, account))
|
util.WriteError(err, w)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
writeJSONObject(w, respBody)
|
|
||||||
|
dnsDomain := h.accountManager.GetDNSDomain()
|
||||||
|
|
||||||
|
respBody := []*api.Peer{}
|
||||||
|
for _, peer := range peers {
|
||||||
|
respBody = append(respBody, toPeerResponse(peer, account, dnsDomain))
|
||||||
|
}
|
||||||
|
util.WriteJSONObject(w, respBody)
|
||||||
return
|
return
|
||||||
default:
|
default:
|
||||||
http.Error(w, "", http.StatusNotFound)
|
util.WriteError(status.Errorf(status.NotFound, "unknown METHOD"), w)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func toPeerResponse(peer *server.Peer, account *server.Account) *api.Peer {
|
func toPeerResponse(peer *server.Peer, account *server.Account, dnsDomain string) *api.Peer {
|
||||||
var groupsInfo []api.GroupMinimum
|
var groupsInfo []api.GroupMinimum
|
||||||
groupsChecked := make(map[string]struct{})
|
groupsChecked := make(map[string]struct{})
|
||||||
for _, group := range account.Groups {
|
for _, group := range account.Groups {
|
||||||
@@ -134,6 +143,10 @@ func toPeerResponse(peer *server.Peer, account *server.Account) *api.Peer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fqdn := peer.DNSLabel
|
||||||
|
if dnsDomain != "" {
|
||||||
|
fqdn = peer.DNSLabel + "." + dnsDomain
|
||||||
|
}
|
||||||
return &api.Peer{
|
return &api.Peer{
|
||||||
Id: peer.IP.String(),
|
Id: peer.IP.String(),
|
||||||
Name: peer.Name,
|
Name: peer.Name,
|
||||||
@@ -144,5 +157,9 @@ func toPeerResponse(peer *server.Peer, account *server.Account) *api.Peer {
|
|||||||
Version: peer.Meta.WtVersion,
|
Version: peer.Meta.WtVersion,
|
||||||
Groups: groupsInfo,
|
Groups: groupsInfo,
|
||||||
SshEnabled: peer.SSHEnabled,
|
SshEnabled: peer.SSHEnabled,
|
||||||
|
Hostname: peer.Meta.Hostname,
|
||||||
|
UserId: &peer.UserID,
|
||||||
|
UiVersion: &peer.Meta.UIVersion,
|
||||||
|
DnsLabel: fqdn,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,17 +16,24 @@ import (
|
|||||||
"github.com/netbirdio/netbird/management/server/mock_server"
|
"github.com/netbirdio/netbird/management/server/mock_server"
|
||||||
)
|
)
|
||||||
|
|
||||||
func initTestMetaData(peer ...*server.Peer) *Peers {
|
func initTestMetaData(peers ...*server.Peer) *Peers {
|
||||||
return &Peers{
|
return &Peers{
|
||||||
accountManager: &mock_server.MockAccountManager{
|
accountManager: &mock_server.MockAccountManager{
|
||||||
GetAccountWithAuthorizationClaimsFunc: func(claims jwtclaims.AuthorizationClaims) (*server.Account, error) {
|
GetPeersFunc: func(accountID, userID string) ([]*server.Peer, error) {
|
||||||
|
return peers, nil
|
||||||
|
},
|
||||||
|
GetAccountFromTokenFunc: func(claims jwtclaims.AuthorizationClaims) (*server.Account, *server.User, error) {
|
||||||
|
user := server.NewAdminUser("test_user")
|
||||||
return &server.Account{
|
return &server.Account{
|
||||||
Id: claims.AccountId,
|
Id: claims.AccountId,
|
||||||
Domain: "hotmail.com",
|
Domain: "hotmail.com",
|
||||||
Peers: map[string]*server.Peer{
|
Peers: map[string]*server.Peer{
|
||||||
"test_peer": peer[0],
|
"test_peer": peers[0],
|
||||||
},
|
},
|
||||||
}, nil
|
Users: map[string]*server.User{
|
||||||
|
"test_user": user,
|
||||||
|
},
|
||||||
|
}, user, nil
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
authAudience: "",
|
authAudience: "",
|
||||||
|
|||||||
@@ -2,15 +2,13 @@ package http
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/netbirdio/netbird/management/server"
|
"github.com/netbirdio/netbird/management/server"
|
||||||
"github.com/netbirdio/netbird/management/server/http/api"
|
"github.com/netbirdio/netbird/management/server/http/api"
|
||||||
|
"github.com/netbirdio/netbird/management/server/http/util"
|
||||||
"github.com/netbirdio/netbird/management/server/jwtclaims"
|
"github.com/netbirdio/netbird/management/server/jwtclaims"
|
||||||
|
"github.com/netbirdio/netbird/management/server/status"
|
||||||
"github.com/netbirdio/netbird/route"
|
"github.com/netbirdio/netbird/route"
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"google.golang.org/grpc/codes"
|
|
||||||
"google.golang.org/grpc/status"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
)
|
)
|
||||||
@@ -33,17 +31,16 @@ func NewRoutes(accountManager server.AccountManager, authAudience string) *Route
|
|||||||
|
|
||||||
// GetAllRoutesHandler returns the list of routes for the account
|
// GetAllRoutesHandler returns the list of routes for the account
|
||||||
func (h *Routes) GetAllRoutesHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *Routes) GetAllRoutesHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
account, err := getJWTAccount(h.accountManager, h.jwtExtractor, h.authAudience, r)
|
claims := h.jwtExtractor.ExtractClaimsFromRequestContext(r, h.authAudience)
|
||||||
|
account, user, err := h.accountManager.GetAccountFromToken(claims)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err)
|
util.WriteError(err, w)
|
||||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
routes, err := h.accountManager.ListRoutes(account.Id)
|
routes, err := h.accountManager.ListRoutes(account.Id, user.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err)
|
util.WriteError(err, w)
|
||||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
apiRoutes := make([]*api.Route, 0)
|
apiRoutes := make([]*api.Route, 0)
|
||||||
@@ -51,20 +48,22 @@ func (h *Routes) GetAllRoutesHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
apiRoutes = append(apiRoutes, toRouteResponse(account, r))
|
apiRoutes = append(apiRoutes, toRouteResponse(account, r))
|
||||||
}
|
}
|
||||||
|
|
||||||
writeJSONObject(w, apiRoutes)
|
util.WriteJSONObject(w, apiRoutes)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateRouteHandler handles route creation request
|
// CreateRouteHandler handles route creation request
|
||||||
func (h *Routes) CreateRouteHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *Routes) CreateRouteHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
account, err := getJWTAccount(h.accountManager, h.jwtExtractor, h.authAudience, r)
|
claims := h.jwtExtractor.ExtractClaimsFromRequestContext(r, h.authAudience)
|
||||||
|
account, _, err := h.accountManager.GetAccountFromToken(claims)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
util.WriteError(err, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var req api.PostApiRoutesJSONRequestBody
|
var req api.PostApiRoutesJSONRequestBody
|
||||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
err = json.NewDecoder(r.Body).Decode(&req)
|
||||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
if err != nil {
|
||||||
|
util.WriteErrorResponse("couldn't parse JSON request", http.StatusBadRequest, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,8 +71,7 @@ func (h *Routes) CreateRouteHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
if req.Peer != "" {
|
if req.Peer != "" {
|
||||||
peer, err := h.accountManager.GetPeerByIP(account.Id, req.Peer)
|
peer, err := h.accountManager.GetPeerByIP(account.Id, req.Peer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err)
|
util.WriteError(err, w)
|
||||||
http.Redirect(w, r, "/", http.StatusUnprocessableEntity)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
peerKey = peer.Key
|
peerKey = peer.Key
|
||||||
@@ -81,57 +79,60 @@ func (h *Routes) CreateRouteHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
_, newPrefix, err := route.ParseNetwork(req.Network)
|
_, newPrefix, err := route.ParseNetwork(req.Network)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, fmt.Sprintf("couldn't parse update prefix %s", req.Network), http.StatusBadRequest)
|
util.WriteError(err, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if utf8.RuneCountInString(req.NetworkId) > route.MaxNetIDChar || req.NetworkId == "" {
|
if utf8.RuneCountInString(req.NetworkId) > route.MaxNetIDChar || req.NetworkId == "" {
|
||||||
http.Error(w, fmt.Sprintf("identifier should be between 1 and %d", route.MaxNetIDChar), http.StatusBadRequest)
|
util.WriteError(status.Errorf(status.InvalidArgument, "identifier should be between 1 and %d",
|
||||||
|
route.MaxNetIDChar), w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
newRoute, err := h.accountManager.CreateRoute(account.Id, newPrefix.String(), peerKey, req.Description, req.NetworkId, req.Masquerade, req.Metric, req.Enabled)
|
newRoute, err := h.accountManager.CreateRoute(account.Id, newPrefix.String(), peerKey, req.Description, req.NetworkId, req.Masquerade, req.Metric, req.Enabled)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err)
|
util.WriteError(err, w)
|
||||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
resp := toRouteResponse(account, newRoute)
|
resp := toRouteResponse(account, newRoute)
|
||||||
|
|
||||||
writeJSONObject(w, &resp)
|
util.WriteJSONObject(w, &resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateRouteHandler handles update to a route identified by a given ID
|
// UpdateRouteHandler handles update to a route identified by a given ID
|
||||||
func (h *Routes) UpdateRouteHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *Routes) UpdateRouteHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
account, err := getJWTAccount(h.accountManager, h.jwtExtractor, h.authAudience, r)
|
claims := h.jwtExtractor.ExtractClaimsFromRequestContext(r, h.authAudience)
|
||||||
|
account, user, err := h.accountManager.GetAccountFromToken(claims)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
util.WriteError(err, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
routeID := vars["id"]
|
routeID := vars["id"]
|
||||||
if len(routeID) == 0 {
|
if len(routeID) == 0 {
|
||||||
http.Error(w, "invalid route Id", http.StatusBadRequest)
|
util.WriteError(status.Errorf(status.InvalidArgument, "invalid route ID"), w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = h.accountManager.GetRoute(account.Id, routeID)
|
_, err = h.accountManager.GetRoute(account.Id, routeID, user.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, fmt.Sprintf("couldn't find route for ID %s", routeID), http.StatusNotFound)
|
util.WriteError(err, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var req api.PutApiRoutesIdJSONBody
|
var req api.PutApiRoutesIdJSONRequestBody
|
||||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
err = json.NewDecoder(r.Body).Decode(&req)
|
||||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
if err != nil {
|
||||||
|
util.WriteErrorResponse("couldn't parse JSON request", http.StatusBadRequest, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
prefixType, newPrefix, err := route.ParseNetwork(req.Network)
|
prefixType, newPrefix, err := route.ParseNetwork(req.Network)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, fmt.Sprintf("couldn't parse update prefix %s for route ID %s", req.Network, routeID), http.StatusBadRequest)
|
util.WriteError(status.Errorf(status.InvalidArgument, "couldn't parse update prefix %s for route ID %s",
|
||||||
|
req.Network, routeID), w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,15 +140,15 @@ func (h *Routes) UpdateRouteHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
if req.Peer != "" {
|
if req.Peer != "" {
|
||||||
peer, err := h.accountManager.GetPeerByIP(account.Id, req.Peer)
|
peer, err := h.accountManager.GetPeerByIP(account.Id, req.Peer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err)
|
util.WriteError(err, w)
|
||||||
http.Redirect(w, r, "/", http.StatusUnprocessableEntity)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
peerKey = peer.Key
|
peerKey = peer.Key
|
||||||
}
|
}
|
||||||
|
|
||||||
if utf8.RuneCountInString(req.NetworkId) > route.MaxNetIDChar || req.NetworkId == "" {
|
if utf8.RuneCountInString(req.NetworkId) > route.MaxNetIDChar || req.NetworkId == "" {
|
||||||
http.Error(w, fmt.Sprintf("identifier should be between 1 and %d", route.MaxNetIDChar), http.StatusBadRequest)
|
util.WriteError(status.Errorf(status.InvalidArgument,
|
||||||
|
"identifier should be between 1 and %d", route.MaxNetIDChar), w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,46 +166,46 @@ func (h *Routes) UpdateRouteHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
err = h.accountManager.SaveRoute(account.Id, newRoute)
|
err = h.accountManager.SaveRoute(account.Id, newRoute)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed updating route \"%s\" under account %s %v", routeID, account.Id, err)
|
util.WriteError(err, w)
|
||||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
resp := toRouteResponse(account, newRoute)
|
resp := toRouteResponse(account, newRoute)
|
||||||
|
|
||||||
writeJSONObject(w, &resp)
|
util.WriteJSONObject(w, &resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PatchRouteHandler handles patch updates to a route identified by a given ID
|
// PatchRouteHandler handles patch updates to a route identified by a given ID
|
||||||
func (h *Routes) PatchRouteHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *Routes) PatchRouteHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
account, err := getJWTAccount(h.accountManager, h.jwtExtractor, h.authAudience, r)
|
claims := h.jwtExtractor.ExtractClaimsFromRequestContext(r, h.authAudience)
|
||||||
|
account, user, err := h.accountManager.GetAccountFromToken(claims)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
util.WriteError(err, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
routeID := vars["id"]
|
routeID := vars["id"]
|
||||||
if len(routeID) == 0 {
|
if len(routeID) == 0 {
|
||||||
http.Error(w, "invalid route ID", http.StatusBadRequest)
|
util.WriteError(status.Errorf(status.InvalidArgument, "invalid route ID"), w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = h.accountManager.GetRoute(account.Id, routeID)
|
_, err = h.accountManager.GetRoute(account.Id, routeID, user.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err)
|
util.WriteError(err, w)
|
||||||
http.Error(w, fmt.Sprintf("couldn't find route ID %s", routeID), http.StatusNotFound)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var req api.PatchApiRoutesIdJSONRequestBody
|
var req api.PatchApiRoutesIdJSONRequestBody
|
||||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
err = json.NewDecoder(r.Body).Decode(&req)
|
||||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
if err != nil {
|
||||||
|
util.WriteErrorResponse("couldn't parse JSON request", http.StatusBadRequest, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(req) == 0 {
|
if len(req) == 0 {
|
||||||
http.Error(w, "no patch instruction received", http.StatusBadRequest)
|
util.WriteError(status.Errorf(status.InvalidArgument, "no patch instruction received"), w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,8 +215,8 @@ func (h *Routes) PatchRouteHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
switch patch.Path {
|
switch patch.Path {
|
||||||
case api.RoutePatchOperationPathNetwork:
|
case api.RoutePatchOperationPathNetwork:
|
||||||
if patch.Op != api.RoutePatchOperationOpReplace {
|
if patch.Op != api.RoutePatchOperationOpReplace {
|
||||||
http.Error(w, fmt.Sprintf("Network field only accepts replace operation, got %s", patch.Op),
|
util.WriteError(status.Errorf(status.InvalidArgument,
|
||||||
http.StatusBadRequest)
|
"network field only accepts replace operation, got %s", patch.Op), w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
operations = append(operations, server.RouteUpdateOperation{
|
operations = append(operations, server.RouteUpdateOperation{
|
||||||
@@ -224,8 +225,8 @@ func (h *Routes) PatchRouteHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
})
|
})
|
||||||
case api.RoutePatchOperationPathDescription:
|
case api.RoutePatchOperationPathDescription:
|
||||||
if patch.Op != api.RoutePatchOperationOpReplace {
|
if patch.Op != api.RoutePatchOperationOpReplace {
|
||||||
http.Error(w, fmt.Sprintf("Description field only accepts replace operation, got %s", patch.Op),
|
util.WriteError(status.Errorf(status.InvalidArgument,
|
||||||
http.StatusBadRequest)
|
"description field only accepts replace operation, got %s", patch.Op), w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
operations = append(operations, server.RouteUpdateOperation{
|
operations = append(operations, server.RouteUpdateOperation{
|
||||||
@@ -234,8 +235,8 @@ func (h *Routes) PatchRouteHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
})
|
})
|
||||||
case api.RoutePatchOperationPathNetworkId:
|
case api.RoutePatchOperationPathNetworkId:
|
||||||
if patch.Op != api.RoutePatchOperationOpReplace {
|
if patch.Op != api.RoutePatchOperationOpReplace {
|
||||||
http.Error(w, fmt.Sprintf("Network Identifier field only accepts replace operation, got %s", patch.Op),
|
util.WriteError(status.Errorf(status.InvalidArgument,
|
||||||
http.StatusBadRequest)
|
"network Identifier field only accepts replace operation, got %s", patch.Op), w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
operations = append(operations, server.RouteUpdateOperation{
|
operations = append(operations, server.RouteUpdateOperation{
|
||||||
@@ -244,21 +245,20 @@ func (h *Routes) PatchRouteHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
})
|
})
|
||||||
case api.RoutePatchOperationPathPeer:
|
case api.RoutePatchOperationPathPeer:
|
||||||
if patch.Op != api.RoutePatchOperationOpReplace {
|
if patch.Op != api.RoutePatchOperationOpReplace {
|
||||||
http.Error(w, fmt.Sprintf("Peer field only accepts replace operation, got %s", patch.Op),
|
util.WriteError(status.Errorf(status.InvalidArgument,
|
||||||
http.StatusBadRequest)
|
"peer field only accepts replace operation, got %s", patch.Op), w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if len(patch.Value) > 1 {
|
if len(patch.Value) > 1 {
|
||||||
http.Error(w, fmt.Sprintf("Value field only accepts 1 value, got %d", len(patch.Value)),
|
util.WriteError(status.Errorf(status.InvalidArgument,
|
||||||
http.StatusBadRequest)
|
"value field only accepts 1 value, got %d", len(patch.Value)), w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
peerValue := patch.Value
|
peerValue := patch.Value
|
||||||
if patch.Value[0] != "" {
|
if patch.Value[0] != "" {
|
||||||
peer, err := h.accountManager.GetPeerByIP(account.Id, patch.Value[0])
|
peer, err := h.accountManager.GetPeerByIP(account.Id, patch.Value[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err)
|
util.WriteError(err, w)
|
||||||
http.Redirect(w, r, "/", http.StatusUnprocessableEntity)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
peerValue = []string{peer.Key}
|
peerValue = []string{peer.Key}
|
||||||
@@ -269,8 +269,9 @@ func (h *Routes) PatchRouteHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
})
|
})
|
||||||
case api.RoutePatchOperationPathMetric:
|
case api.RoutePatchOperationPathMetric:
|
||||||
if patch.Op != api.RoutePatchOperationOpReplace {
|
if patch.Op != api.RoutePatchOperationOpReplace {
|
||||||
http.Error(w, fmt.Sprintf("Metric field only accepts replace operation, got %s", patch.Op),
|
util.WriteError(status.Errorf(status.InvalidArgument,
|
||||||
http.StatusBadRequest)
|
"metric field only accepts replace operation, got %s", patch.Op), w)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
operations = append(operations, server.RouteUpdateOperation{
|
operations = append(operations, server.RouteUpdateOperation{
|
||||||
@@ -279,8 +280,8 @@ func (h *Routes) PatchRouteHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
})
|
})
|
||||||
case api.RoutePatchOperationPathMasquerade:
|
case api.RoutePatchOperationPathMasquerade:
|
||||||
if patch.Op != api.RoutePatchOperationOpReplace {
|
if patch.Op != api.RoutePatchOperationOpReplace {
|
||||||
http.Error(w, fmt.Sprintf("Masquerade field only accepts replace operation, got %s", patch.Op),
|
util.WriteError(status.Errorf(status.InvalidArgument,
|
||||||
http.StatusBadRequest)
|
"masquerade field only accepts replace operation, got %s", patch.Op), w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
operations = append(operations, server.RouteUpdateOperation{
|
operations = append(operations, server.RouteUpdateOperation{
|
||||||
@@ -289,8 +290,8 @@ func (h *Routes) PatchRouteHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
})
|
})
|
||||||
case api.RoutePatchOperationPathEnabled:
|
case api.RoutePatchOperationPathEnabled:
|
||||||
if patch.Op != api.RoutePatchOperationOpReplace {
|
if patch.Op != api.RoutePatchOperationOpReplace {
|
||||||
http.Error(w, fmt.Sprintf("Enabled field only accepts replace operation, got %s", patch.Op),
|
util.WriteError(status.Errorf(status.InvalidArgument,
|
||||||
http.StatusBadRequest)
|
"enabled field only accepts replace operation, got %s", patch.Op), w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
operations = append(operations, server.RouteUpdateOperation{
|
operations = append(operations, server.RouteUpdateOperation{
|
||||||
@@ -298,85 +299,68 @@ func (h *Routes) PatchRouteHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
Values: patch.Value,
|
Values: patch.Value,
|
||||||
})
|
})
|
||||||
default:
|
default:
|
||||||
http.Error(w, "invalid patch path", http.StatusBadRequest)
|
util.WriteError(status.Errorf(status.InvalidArgument, "invalid patch path"), w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
route, err := h.accountManager.UpdateRoute(account.Id, routeID, operations)
|
route, err := h.accountManager.UpdateRoute(account.Id, routeID, operations)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errStatus, ok := status.FromError(err)
|
util.WriteError(err, w)
|
||||||
if ok && errStatus.Code() == codes.Internal {
|
|
||||||
http.Error(w, errStatus.String(), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if ok && errStatus.Code() == codes.NotFound {
|
|
||||||
http.Error(w, errStatus.String(), http.StatusNotFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if ok && errStatus.Code() == codes.InvalidArgument {
|
|
||||||
http.Error(w, errStatus.String(), http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Errorf("failed updating route %s under account %s %v", routeID, account.Id, err)
|
|
||||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
resp := toRouteResponse(account, route)
|
resp := toRouteResponse(account, route)
|
||||||
|
|
||||||
writeJSONObject(w, &resp)
|
util.WriteJSONObject(w, &resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteRouteHandler handles route deletion request
|
// DeleteRouteHandler handles route deletion request
|
||||||
func (h *Routes) DeleteRouteHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *Routes) DeleteRouteHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
account, err := getJWTAccount(h.accountManager, h.jwtExtractor, h.authAudience, r)
|
claims := h.jwtExtractor.ExtractClaimsFromRequestContext(r, h.authAudience)
|
||||||
|
account, _, err := h.accountManager.GetAccountFromToken(claims)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
util.WriteError(err, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
routeID := mux.Vars(r)["id"]
|
routeID := mux.Vars(r)["id"]
|
||||||
if len(routeID) == 0 {
|
if len(routeID) == 0 {
|
||||||
http.Error(w, "invalid route ID", http.StatusBadRequest)
|
util.WriteError(status.Errorf(status.InvalidArgument, "invalid route ID"), w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = h.accountManager.DeleteRoute(account.Id, routeID)
|
err = h.accountManager.DeleteRoute(account.Id, routeID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed delete route %s under account %s %v", routeID, account.Id, err)
|
util.WriteError(err, w)
|
||||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
writeJSONObject(w, "")
|
util.WriteJSONObject(w, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRouteHandler handles a route Get request identified by ID
|
// GetRouteHandler handles a route Get request identified by ID
|
||||||
func (h *Routes) GetRouteHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *Routes) GetRouteHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
account, err := getJWTAccount(h.accountManager, h.jwtExtractor, h.authAudience, r)
|
claims := h.jwtExtractor.ExtractClaimsFromRequestContext(r, h.authAudience)
|
||||||
|
account, user, err := h.accountManager.GetAccountFromToken(claims)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
util.WriteError(err, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
routeID := mux.Vars(r)["id"]
|
routeID := mux.Vars(r)["id"]
|
||||||
if len(routeID) == 0 {
|
if len(routeID) == 0 {
|
||||||
http.Error(w, "invalid route ID", http.StatusBadRequest)
|
util.WriteError(status.Errorf(status.InvalidArgument, "invalid route ID"), w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
foundRoute, err := h.accountManager.GetRoute(account.Id, routeID)
|
foundRoute, err := h.accountManager.GetRoute(account.Id, routeID, user.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "route not found", http.StatusNotFound)
|
util.WriteError(status.Errorf(status.NotFound, "route not found"), w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
writeJSONObject(w, toRouteResponse(account, foundRoute))
|
util.WriteJSONObject(w, toRouteResponse(account, foundRoute))
|
||||||
}
|
}
|
||||||
|
|
||||||
func toRouteResponse(account *server.Account, serverRoute *route.Route) *api.Route {
|
func toRouteResponse(account *server.Account, serverRoute *route.Route) *api.Route {
|
||||||
|
|||||||
@@ -5,9 +5,8 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/netbirdio/netbird/management/server/http/api"
|
"github.com/netbirdio/netbird/management/server/http/api"
|
||||||
|
"github.com/netbirdio/netbird/management/server/status"
|
||||||
"github.com/netbirdio/netbird/route"
|
"github.com/netbirdio/netbird/route"
|
||||||
"google.golang.org/grpc/codes"
|
|
||||||
"google.golang.org/grpc/status"
|
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
@@ -51,16 +50,19 @@ var testingAccount = &server.Account{
|
|||||||
IP: netip.MustParseAddr(existingPeerID).AsSlice(),
|
IP: netip.MustParseAddr(existingPeerID).AsSlice(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Users: map[string]*server.User{
|
||||||
|
"test_user": server.NewAdminUser("test_user"),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func initRoutesTestData() *Routes {
|
func initRoutesTestData() *Routes {
|
||||||
return &Routes{
|
return &Routes{
|
||||||
accountManager: &mock_server.MockAccountManager{
|
accountManager: &mock_server.MockAccountManager{
|
||||||
GetRouteFunc: func(_, routeID string) (*route.Route, error) {
|
GetRouteFunc: func(_, routeID, _ string) (*route.Route, error) {
|
||||||
if routeID == existingRouteID {
|
if routeID == existingRouteID {
|
||||||
return baseExistingRoute, nil
|
return baseExistingRoute, nil
|
||||||
}
|
}
|
||||||
return nil, status.Errorf(codes.NotFound, "route with ID %s not found", routeID)
|
return nil, status.Errorf(status.NotFound, "route with ID %s not found", routeID)
|
||||||
},
|
},
|
||||||
CreateRouteFunc: func(accountID string, network, peer, description, netID string, masquerade bool, metric int, enabled bool) (*route.Route, error) {
|
CreateRouteFunc: func(accountID string, network, peer, description, netID string, masquerade bool, metric int, enabled bool) (*route.Route, error) {
|
||||||
networkType, p, _ := route.ParseNetwork(network)
|
networkType, p, _ := route.ParseNetwork(network)
|
||||||
@@ -78,12 +80,15 @@ func initRoutesTestData() *Routes {
|
|||||||
SaveRouteFunc: func(_ string, _ *route.Route) error {
|
SaveRouteFunc: func(_ string, _ *route.Route) error {
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
DeleteRouteFunc: func(_ string, _ string) error {
|
DeleteRouteFunc: func(_ string, peerIP string) error {
|
||||||
|
if peerIP != existingRouteID {
|
||||||
|
return status.Errorf(status.NotFound, "Peer with ID %s not found", peerIP)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
GetPeerByIPFunc: func(_ string, peerIP string) (*server.Peer, error) {
|
GetPeerByIPFunc: func(_ string, peerIP string) (*server.Peer, error) {
|
||||||
if peerIP != existingPeerID {
|
if peerIP != existingPeerID {
|
||||||
return nil, status.Errorf(codes.NotFound, "Peer with ID %s not found", peerIP)
|
return nil, status.Errorf(status.NotFound, "Peer with ID %s not found", peerIP)
|
||||||
}
|
}
|
||||||
return &server.Peer{
|
return &server.Peer{
|
||||||
Key: existingPeerKey,
|
Key: existingPeerKey,
|
||||||
@@ -93,7 +98,7 @@ func initRoutesTestData() *Routes {
|
|||||||
UpdateRouteFunc: func(_ string, routeID string, operations []server.RouteUpdateOperation) (*route.Route, error) {
|
UpdateRouteFunc: func(_ string, routeID string, operations []server.RouteUpdateOperation) (*route.Route, error) {
|
||||||
routeToUpdate := baseExistingRoute
|
routeToUpdate := baseExistingRoute
|
||||||
if routeID != routeToUpdate.ID {
|
if routeID != routeToUpdate.ID {
|
||||||
return nil, status.Errorf(codes.NotFound, "route %s no longer exists", routeID)
|
return nil, status.Errorf(status.NotFound, "route %s no longer exists", routeID)
|
||||||
}
|
}
|
||||||
for _, operation := range operations {
|
for _, operation := range operations {
|
||||||
switch operation.Type {
|
switch operation.Type {
|
||||||
@@ -117,8 +122,8 @@ func initRoutesTestData() *Routes {
|
|||||||
}
|
}
|
||||||
return routeToUpdate, nil
|
return routeToUpdate, nil
|
||||||
},
|
},
|
||||||
GetAccountWithAuthorizationClaimsFunc: func(_ jwtclaims.AuthorizationClaims) (*server.Account, error) {
|
GetAccountFromTokenFunc: func(_ jwtclaims.AuthorizationClaims) (*server.Account, *server.User, error) {
|
||||||
return testingAccount, nil
|
return testingAccount, testingAccount.Users["test_user"], nil
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
authAudience: "",
|
authAudience: "",
|
||||||
@@ -155,7 +160,7 @@ func TestRoutesHandlers(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "Get Not Existing Route",
|
name: "Get Not Existing Route",
|
||||||
requestType: http.MethodGet,
|
requestType: http.MethodGet,
|
||||||
requestPath: "/api/rules/" + notFoundRouteID,
|
requestPath: "/api/routes/" + notFoundRouteID,
|
||||||
expectedStatus: http.StatusNotFound,
|
expectedStatus: http.StatusNotFound,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -168,7 +173,7 @@ func TestRoutesHandlers(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "Delete Not Existing Route",
|
name: "Delete Not Existing Route",
|
||||||
requestType: http.MethodDelete,
|
requestType: http.MethodDelete,
|
||||||
requestPath: "/api/rules/" + notFoundRouteID,
|
requestPath: "/api/routes/" + notFoundRouteID,
|
||||||
expectedStatus: http.StatusNotFound,
|
expectedStatus: http.StatusNotFound,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -195,15 +200,15 @@ func TestRoutesHandlers(t *testing.T) {
|
|||||||
requestType: http.MethodPost,
|
requestType: http.MethodPost,
|
||||||
requestPath: "/api/routes",
|
requestPath: "/api/routes",
|
||||||
requestBody: bytes.NewBufferString(fmt.Sprintf("{\"Description\":\"Post\",\"Network\":\"192.168.0.0/16\",\"network_id\":\"awesomeNet\",\"Peer\":\"%s\"}", notFoundPeerID)),
|
requestBody: bytes.NewBufferString(fmt.Sprintf("{\"Description\":\"Post\",\"Network\":\"192.168.0.0/16\",\"network_id\":\"awesomeNet\",\"Peer\":\"%s\"}", notFoundPeerID)),
|
||||||
expectedStatus: http.StatusUnprocessableEntity,
|
expectedStatus: http.StatusNotFound,
|
||||||
expectedBody: false,
|
expectedBody: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "POST Not Invalid Network Identifier",
|
name: "POST Invalid Network Identifier",
|
||||||
requestType: http.MethodPost,
|
requestType: http.MethodPost,
|
||||||
requestPath: "/api/routes",
|
requestPath: "/api/routes",
|
||||||
requestBody: bytes.NewBufferString(fmt.Sprintf("{\"Description\":\"Post\",\"Network\":\"192.168.0.0/16\",\"network_id\":\"12345678901234567890qwertyuiopqwertyuiop1\",\"Peer\":\"%s\"}", existingPeerID)),
|
requestBody: bytes.NewBufferString(fmt.Sprintf("{\"Description\":\"Post\",\"Network\":\"192.168.0.0/16\",\"network_id\":\"12345678901234567890qwertyuiopqwertyuiop1\",\"Peer\":\"%s\"}", existingPeerID)),
|
||||||
expectedStatus: http.StatusBadRequest,
|
expectedStatus: http.StatusUnprocessableEntity,
|
||||||
expectedBody: false,
|
expectedBody: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -211,7 +216,7 @@ func TestRoutesHandlers(t *testing.T) {
|
|||||||
requestType: http.MethodPost,
|
requestType: http.MethodPost,
|
||||||
requestPath: "/api/routes",
|
requestPath: "/api/routes",
|
||||||
requestBody: bytes.NewBufferString(fmt.Sprintf("{\"Description\":\"Post\",\"Network\":\"192.168.0.0/34\",\"network_id\":\"awesomeNet\",\"Peer\":\"%s\"}", existingPeerID)),
|
requestBody: bytes.NewBufferString(fmt.Sprintf("{\"Description\":\"Post\",\"Network\":\"192.168.0.0/34\",\"network_id\":\"awesomeNet\",\"Peer\":\"%s\"}", existingPeerID)),
|
||||||
expectedStatus: http.StatusBadRequest,
|
expectedStatus: http.StatusUnprocessableEntity,
|
||||||
expectedBody: false,
|
expectedBody: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -245,7 +250,7 @@ func TestRoutesHandlers(t *testing.T) {
|
|||||||
requestType: http.MethodPut,
|
requestType: http.MethodPut,
|
||||||
requestPath: "/api/routes/" + existingRouteID,
|
requestPath: "/api/routes/" + existingRouteID,
|
||||||
requestBody: bytes.NewBufferString(fmt.Sprintf("{\"Description\":\"Post\",\"Network\":\"192.168.0.0/16\",\"network_id\":\"awesomeNet\",\"Peer\":\"%s\"}", notFoundPeerID)),
|
requestBody: bytes.NewBufferString(fmt.Sprintf("{\"Description\":\"Post\",\"Network\":\"192.168.0.0/16\",\"network_id\":\"awesomeNet\",\"Peer\":\"%s\"}", notFoundPeerID)),
|
||||||
expectedStatus: http.StatusUnprocessableEntity,
|
expectedStatus: http.StatusNotFound,
|
||||||
expectedBody: false,
|
expectedBody: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -253,7 +258,7 @@ func TestRoutesHandlers(t *testing.T) {
|
|||||||
requestType: http.MethodPut,
|
requestType: http.MethodPut,
|
||||||
requestPath: "/api/routes/" + existingRouteID,
|
requestPath: "/api/routes/" + existingRouteID,
|
||||||
requestBody: bytes.NewBufferString(fmt.Sprintf("{\"Description\":\"Post\",\"Network\":\"192.168.0.0/16\",\"network_id\":\"12345678901234567890qwertyuiopqwertyuiop1\",\"Peer\":\"%s\"}", existingPeerID)),
|
requestBody: bytes.NewBufferString(fmt.Sprintf("{\"Description\":\"Post\",\"Network\":\"192.168.0.0/16\",\"network_id\":\"12345678901234567890qwertyuiopqwertyuiop1\",\"Peer\":\"%s\"}", existingPeerID)),
|
||||||
expectedStatus: http.StatusBadRequest,
|
expectedStatus: http.StatusUnprocessableEntity,
|
||||||
expectedBody: false,
|
expectedBody: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -261,7 +266,7 @@ func TestRoutesHandlers(t *testing.T) {
|
|||||||
requestType: http.MethodPut,
|
requestType: http.MethodPut,
|
||||||
requestPath: "/api/routes/" + existingRouteID,
|
requestPath: "/api/routes/" + existingRouteID,
|
||||||
requestBody: bytes.NewBufferString(fmt.Sprintf("{\"Description\":\"Post\",\"Network\":\"192.168.0.0/34\",\"network_id\":\"awesomeNet\",\"Peer\":\"%s\"}", existingPeerID)),
|
requestBody: bytes.NewBufferString(fmt.Sprintf("{\"Description\":\"Post\",\"Network\":\"192.168.0.0/34\",\"network_id\":\"awesomeNet\",\"Peer\":\"%s\"}", existingPeerID)),
|
||||||
expectedStatus: http.StatusBadRequest,
|
expectedStatus: http.StatusUnprocessableEntity,
|
||||||
expectedBody: false,
|
expectedBody: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -306,7 +311,7 @@ func TestRoutesHandlers(t *testing.T) {
|
|||||||
requestType: http.MethodPatch,
|
requestType: http.MethodPatch,
|
||||||
requestPath: "/api/routes/" + existingRouteID,
|
requestPath: "/api/routes/" + existingRouteID,
|
||||||
requestBody: bytes.NewBufferString(fmt.Sprintf("[{\"op\":\"replace\",\"path\":\"peer\",\"value\":[\"%s\"]}]", notFoundPeerID)),
|
requestBody: bytes.NewBufferString(fmt.Sprintf("[{\"op\":\"replace\",\"path\":\"peer\",\"value\":[\"%s\"]}]", notFoundPeerID)),
|
||||||
expectedStatus: http.StatusUnprocessableEntity,
|
expectedStatus: http.StatusNotFound,
|
||||||
expectedBody: false,
|
expectedBody: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,15 +2,13 @@ package http
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/netbirdio/netbird/management/server"
|
"github.com/netbirdio/netbird/management/server"
|
||||||
"github.com/netbirdio/netbird/management/server/http/api"
|
"github.com/netbirdio/netbird/management/server/http/api"
|
||||||
|
"github.com/netbirdio/netbird/management/server/http/util"
|
||||||
"github.com/netbirdio/netbird/management/server/jwtclaims"
|
"github.com/netbirdio/netbird/management/server/jwtclaims"
|
||||||
|
"github.com/netbirdio/netbird/management/server/status"
|
||||||
"github.com/rs/xid"
|
"github.com/rs/xid"
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"google.golang.org/grpc/codes"
|
|
||||||
"google.golang.org/grpc/status"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -31,50 +29,56 @@ func NewRules(accountManager server.AccountManager, authAudience string) *Rules
|
|||||||
|
|
||||||
// GetAllRulesHandler list for the account
|
// GetAllRulesHandler list for the account
|
||||||
func (h *Rules) GetAllRulesHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *Rules) GetAllRulesHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
account, err := getJWTAccount(h.accountManager, h.jwtExtractor, h.authAudience, r)
|
claims := h.jwtExtractor.ExtractClaimsFromRequestContext(r, h.authAudience)
|
||||||
|
account, user, err := h.accountManager.GetAccountFromToken(claims)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err)
|
util.WriteError(err, w)
|
||||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
accountRules, err := h.accountManager.ListRules(account.Id, user.Id)
|
||||||
|
if err != nil {
|
||||||
|
util.WriteError(err, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
rules := []*api.Rule{}
|
rules := []*api.Rule{}
|
||||||
for _, r := range account.Rules {
|
for _, r := range accountRules {
|
||||||
rules = append(rules, toRuleResponse(account, r))
|
rules = append(rules, toRuleResponse(account, r))
|
||||||
}
|
}
|
||||||
|
|
||||||
writeJSONObject(w, rules)
|
util.WriteJSONObject(w, rules)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateRuleHandler handles update to a rule identified by a given ID
|
// UpdateRuleHandler handles update to a rule identified by a given ID
|
||||||
func (h *Rules) UpdateRuleHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *Rules) UpdateRuleHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
account, err := getJWTAccount(h.accountManager, h.jwtExtractor, h.authAudience, r)
|
claims := h.jwtExtractor.ExtractClaimsFromRequestContext(r, h.authAudience)
|
||||||
|
account, _, err := h.accountManager.GetAccountFromToken(claims)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
util.WriteError(err, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
ruleID := vars["id"]
|
ruleID := vars["id"]
|
||||||
if len(ruleID) == 0 {
|
if len(ruleID) == 0 {
|
||||||
http.Error(w, "invalid rule Id", http.StatusBadRequest)
|
util.WriteError(status.Errorf(status.InvalidArgument, "invalid rule ID"), w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_, ok := account.Rules[ruleID]
|
_, ok := account.Rules[ruleID]
|
||||||
if !ok {
|
if !ok {
|
||||||
http.Error(w, fmt.Sprintf("couldn't find rule id %s", ruleID), http.StatusNotFound)
|
util.WriteError(status.Errorf(status.NotFound, "couldn't find rule id %s", ruleID), w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var req api.PutApiRulesIdJSONRequestBody
|
var req api.PutApiRulesIdJSONRequestBody
|
||||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
err = json.NewDecoder(r.Body).Decode(&req)
|
||||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
if err != nil {
|
||||||
return
|
util.WriteErrorResponse("couldn't parse JSON request", http.StatusBadRequest, w)
|
||||||
}
|
}
|
||||||
|
|
||||||
if req.Name == "" {
|
if req.Name == "" {
|
||||||
http.Error(w, "Rule name shouldn't be empty", http.StatusUnprocessableEntity)
|
util.WriteError(status.Errorf(status.InvalidArgument, "rule name shouldn't be empty"), w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,50 +105,52 @@ func (h *Rules) UpdateRuleHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
case server.TrafficFlowBidirectString:
|
case server.TrafficFlowBidirectString:
|
||||||
rule.Flow = server.TrafficFlowBidirect
|
rule.Flow = server.TrafficFlowBidirect
|
||||||
default:
|
default:
|
||||||
http.Error(w, "unknown flow type", http.StatusBadRequest)
|
util.WriteError(status.Errorf(status.InvalidArgument, "unknown flow type"), w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := h.accountManager.SaveRule(account.Id, &rule); err != nil {
|
err = h.accountManager.SaveRule(account.Id, &rule)
|
||||||
log.Errorf("failed updating rule \"%s\" under account %s %v", ruleID, account.Id, err)
|
if err != nil {
|
||||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
util.WriteError(err, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
resp := toRuleResponse(account, &rule)
|
resp := toRuleResponse(account, &rule)
|
||||||
|
|
||||||
writeJSONObject(w, &resp)
|
util.WriteJSONObject(w, &resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PatchRuleHandler handles patch updates to a rule identified by a given ID
|
// PatchRuleHandler handles patch updates to a rule identified by a given ID
|
||||||
func (h *Rules) PatchRuleHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *Rules) PatchRuleHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
account, err := getJWTAccount(h.accountManager, h.jwtExtractor, h.authAudience, r)
|
claims := h.jwtExtractor.ExtractClaimsFromRequestContext(r, h.authAudience)
|
||||||
|
account, _, err := h.accountManager.GetAccountFromToken(claims)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
util.WriteError(err, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
ruleID := vars["id"]
|
ruleID := vars["id"]
|
||||||
if len(ruleID) == 0 {
|
if len(ruleID) == 0 {
|
||||||
http.Error(w, "invalid rule Id", http.StatusBadRequest)
|
util.WriteError(status.Errorf(status.InvalidArgument, "invalid rule ID"), w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_, ok := account.Rules[ruleID]
|
_, ok := account.Rules[ruleID]
|
||||||
if !ok {
|
if !ok {
|
||||||
http.Error(w, fmt.Sprintf("couldn't find rule id %s", ruleID), http.StatusNotFound)
|
util.WriteError(status.Errorf(status.NotFound, "couldn't find rule ID %s", ruleID), w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var req api.PatchApiRulesIdJSONRequestBody
|
var req api.PatchApiRulesIdJSONRequestBody
|
||||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
err = json.NewDecoder(r.Body).Decode(&req)
|
||||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
if err != nil {
|
||||||
|
util.WriteErrorResponse("couldn't parse JSON request", http.StatusBadRequest, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(req) == 0 {
|
if len(req) == 0 {
|
||||||
http.Error(w, "no patch instruction received", http.StatusBadRequest)
|
util.WriteError(status.Errorf(status.InvalidArgument, "no patch instruction received"), w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,12 +160,12 @@ func (h *Rules) PatchRuleHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
switch patch.Path {
|
switch patch.Path {
|
||||||
case api.RulePatchOperationPathName:
|
case api.RulePatchOperationPathName:
|
||||||
if patch.Op != api.RulePatchOperationOpReplace {
|
if patch.Op != api.RulePatchOperationOpReplace {
|
||||||
http.Error(w, fmt.Sprintf("Name field only accepts replace operation, got %s", patch.Op),
|
util.WriteError(status.Errorf(status.InvalidArgument,
|
||||||
http.StatusBadRequest)
|
"name field only accepts replace operation, got %s", patch.Op), w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if len(patch.Value) == 0 || patch.Value[0] == "" {
|
if len(patch.Value) == 0 || patch.Value[0] == "" {
|
||||||
http.Error(w, "Rule name shouldn't be empty", http.StatusUnprocessableEntity)
|
util.WriteError(status.Errorf(status.InvalidArgument, "rule name shouldn't be empty"), w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
operations = append(operations, server.RuleUpdateOperation{
|
operations = append(operations, server.RuleUpdateOperation{
|
||||||
@@ -168,8 +174,8 @@ func (h *Rules) PatchRuleHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
})
|
})
|
||||||
case api.RulePatchOperationPathDescription:
|
case api.RulePatchOperationPathDescription:
|
||||||
if patch.Op != api.RulePatchOperationOpReplace {
|
if patch.Op != api.RulePatchOperationOpReplace {
|
||||||
http.Error(w, fmt.Sprintf("Description field only accepts replace operation, got %s", patch.Op),
|
util.WriteError(status.Errorf(status.InvalidArgument,
|
||||||
http.StatusBadRequest)
|
"description field only accepts replace operation, got %s", patch.Op), w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
operations = append(operations, server.RuleUpdateOperation{
|
operations = append(operations, server.RuleUpdateOperation{
|
||||||
@@ -178,8 +184,8 @@ func (h *Rules) PatchRuleHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
})
|
})
|
||||||
case api.RulePatchOperationPathFlow:
|
case api.RulePatchOperationPathFlow:
|
||||||
if patch.Op != api.RulePatchOperationOpReplace {
|
if patch.Op != api.RulePatchOperationOpReplace {
|
||||||
http.Error(w, fmt.Sprintf("Flow field only accepts replace operation, got %s", patch.Op),
|
util.WriteError(status.Errorf(status.InvalidArgument,
|
||||||
http.StatusBadRequest)
|
"flow field only accepts replace operation, got %s", patch.Op), w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
operations = append(operations, server.RuleUpdateOperation{
|
operations = append(operations, server.RuleUpdateOperation{
|
||||||
@@ -188,8 +194,8 @@ func (h *Rules) PatchRuleHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
})
|
})
|
||||||
case api.RulePatchOperationPathDisabled:
|
case api.RulePatchOperationPathDisabled:
|
||||||
if patch.Op != api.RulePatchOperationOpReplace {
|
if patch.Op != api.RulePatchOperationOpReplace {
|
||||||
http.Error(w, fmt.Sprintf("Disabled field only accepts replace operation, got %s", patch.Op),
|
util.WriteError(status.Errorf(status.InvalidArgument,
|
||||||
http.StatusBadRequest)
|
"disabled field only accepts replace operation, got %s", patch.Op), w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
operations = append(operations, server.RuleUpdateOperation{
|
operations = append(operations, server.RuleUpdateOperation{
|
||||||
@@ -214,7 +220,8 @@ func (h *Rules) PatchRuleHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
Values: patch.Value,
|
Values: patch.Value,
|
||||||
})
|
})
|
||||||
default:
|
default:
|
||||||
http.Error(w, "invalid operation, \"%s\", for Source field", http.StatusBadRequest)
|
util.WriteError(status.Errorf(status.InvalidArgument,
|
||||||
|
"invalid operation \"%s\" on Source field", patch.Op), w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case api.RulePatchOperationPathDestinations:
|
case api.RulePatchOperationPathDestinations:
|
||||||
@@ -235,11 +242,12 @@ func (h *Rules) PatchRuleHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
Values: patch.Value,
|
Values: patch.Value,
|
||||||
})
|
})
|
||||||
default:
|
default:
|
||||||
http.Error(w, "invalid operation, \"%s\", for Destination field", http.StatusBadRequest)
|
util.WriteError(status.Errorf(status.InvalidArgument,
|
||||||
|
"invalid operation \"%s\" on Destination field", patch.Op), w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
http.Error(w, "invalid patch path", http.StatusBadRequest)
|
util.WriteError(status.Errorf(status.InvalidArgument, "invalid patch path"), w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -247,48 +255,33 @@ func (h *Rules) PatchRuleHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
rule, err := h.accountManager.UpdateRule(account.Id, ruleID, operations)
|
rule, err := h.accountManager.UpdateRule(account.Id, ruleID, operations)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errStatus, ok := status.FromError(err)
|
util.WriteError(err, w)
|
||||||
if ok && errStatus.Code() == codes.Internal {
|
|
||||||
http.Error(w, errStatus.String(), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if ok && errStatus.Code() == codes.NotFound {
|
|
||||||
http.Error(w, errStatus.String(), http.StatusNotFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if ok && errStatus.Code() == codes.InvalidArgument {
|
|
||||||
http.Error(w, errStatus.String(), http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Errorf("failed updating rule %s under account %s %v", ruleID, account.Id, err)
|
|
||||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
resp := toRuleResponse(account, rule)
|
resp := toRuleResponse(account, rule)
|
||||||
|
|
||||||
writeJSONObject(w, &resp)
|
util.WriteJSONObject(w, &resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateRuleHandler handles rule creation request
|
// CreateRuleHandler handles rule creation request
|
||||||
func (h *Rules) CreateRuleHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *Rules) CreateRuleHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
account, err := getJWTAccount(h.accountManager, h.jwtExtractor, h.authAudience, r)
|
claims := h.jwtExtractor.ExtractClaimsFromRequestContext(r, h.authAudience)
|
||||||
|
account, _, err := h.accountManager.GetAccountFromToken(claims)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
util.WriteError(err, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var req api.PostApiRulesJSONRequestBody
|
var req api.PostApiRulesJSONRequestBody
|
||||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
err = json.NewDecoder(r.Body).Decode(&req)
|
||||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
if err != nil {
|
||||||
|
util.WriteErrorResponse("couldn't parse JSON request", http.StatusBadRequest, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if req.Name == "" {
|
if req.Name == "" {
|
||||||
http.Error(w, "Rule name shouldn't be empty", http.StatusUnprocessableEntity)
|
util.WriteError(status.Errorf(status.InvalidArgument, "rule name shouldn't be empty"), w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -315,50 +308,52 @@ func (h *Rules) CreateRuleHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
case server.TrafficFlowBidirectString:
|
case server.TrafficFlowBidirectString:
|
||||||
rule.Flow = server.TrafficFlowBidirect
|
rule.Flow = server.TrafficFlowBidirect
|
||||||
default:
|
default:
|
||||||
http.Error(w, "unknown flow type", http.StatusBadRequest)
|
util.WriteError(status.Errorf(status.InvalidArgument, "unknown flow type"), w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := h.accountManager.SaveRule(account.Id, &rule); err != nil {
|
err = h.accountManager.SaveRule(account.Id, &rule)
|
||||||
log.Errorf("failed creating rule \"%s\" under account %s %v", req.Name, account.Id, err)
|
if err != nil {
|
||||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
util.WriteError(err, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
resp := toRuleResponse(account, &rule)
|
resp := toRuleResponse(account, &rule)
|
||||||
|
|
||||||
writeJSONObject(w, &resp)
|
util.WriteJSONObject(w, &resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteRuleHandler handles rule deletion request
|
// DeleteRuleHandler handles rule deletion request
|
||||||
func (h *Rules) DeleteRuleHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *Rules) DeleteRuleHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
account, err := getJWTAccount(h.accountManager, h.jwtExtractor, h.authAudience, r)
|
claims := h.jwtExtractor.ExtractClaimsFromRequestContext(r, h.authAudience)
|
||||||
|
account, _, err := h.accountManager.GetAccountFromToken(claims)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
util.WriteError(err, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
aID := account.Id
|
aID := account.Id
|
||||||
|
|
||||||
rID := mux.Vars(r)["id"]
|
rID := mux.Vars(r)["id"]
|
||||||
if len(rID) == 0 {
|
if len(rID) == 0 {
|
||||||
http.Error(w, "invalid rule ID", http.StatusBadRequest)
|
util.WriteError(status.Errorf(status.InvalidArgument, "invalid rule ID"), w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := h.accountManager.DeleteRule(aID, rID); err != nil {
|
err = h.accountManager.DeleteRule(aID, rID)
|
||||||
log.Errorf("failed delete rule %s under account %s %v", rID, aID, err)
|
if err != nil {
|
||||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
util.WriteError(err, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
writeJSONObject(w, "")
|
util.WriteJSONObject(w, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRuleHandler handles a group Get request identified by ID
|
// GetRuleHandler handles a group Get request identified by ID
|
||||||
func (h *Rules) GetRuleHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *Rules) GetRuleHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
account, err := getJWTAccount(h.accountManager, h.jwtExtractor, h.authAudience, r)
|
claims := h.jwtExtractor.ExtractClaimsFromRequestContext(r, h.authAudience)
|
||||||
|
account, user, err := h.accountManager.GetAccountFromToken(claims)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
util.WriteError(err, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -366,19 +361,19 @@ func (h *Rules) GetRuleHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
case http.MethodGet:
|
case http.MethodGet:
|
||||||
ruleID := mux.Vars(r)["id"]
|
ruleID := mux.Vars(r)["id"]
|
||||||
if len(ruleID) == 0 {
|
if len(ruleID) == 0 {
|
||||||
http.Error(w, "invalid rule ID", http.StatusBadRequest)
|
util.WriteError(status.Errorf(status.InvalidArgument, "invalid rule ID"), w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
rule, err := h.accountManager.GetRule(account.Id, ruleID)
|
rule, err := h.accountManager.GetRule(account.Id, ruleID, user.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "rule not found", http.StatusNotFound)
|
util.WriteError(status.Errorf(status.NotFound, "rule not found"), w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
writeJSONObject(w, toRuleResponse(account, rule))
|
util.WriteJSONObject(w, toRuleResponse(account, rule))
|
||||||
default:
|
default:
|
||||||
http.Error(w, "", http.StatusNotFound)
|
util.WriteError(status.Errorf(status.NotFound, "method not found"), w)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ func initRulesTestData(rules ...*server.Rule) *Rules {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
GetRuleFunc: func(_, ruleID string) (*server.Rule, error) {
|
GetRuleFunc: func(_, ruleID, _ string) (*server.Rule, error) {
|
||||||
if ruleID != "idoftherule" {
|
if ruleID != "idoftherule" {
|
||||||
return nil, fmt.Errorf("not found")
|
return nil, fmt.Errorf("not found")
|
||||||
}
|
}
|
||||||
@@ -66,16 +66,20 @@ func initRulesTestData(rules ...*server.Rule) *Rules {
|
|||||||
}
|
}
|
||||||
return &rule, nil
|
return &rule, nil
|
||||||
},
|
},
|
||||||
GetAccountWithAuthorizationClaimsFunc: func(claims jwtclaims.AuthorizationClaims) (*server.Account, error) {
|
GetAccountFromTokenFunc: func(claims jwtclaims.AuthorizationClaims) (*server.Account, *server.User, error) {
|
||||||
|
user := server.NewAdminUser("test_user")
|
||||||
return &server.Account{
|
return &server.Account{
|
||||||
Id: claims.AccountId,
|
Id: claims.AccountId,
|
||||||
Domain: "hotmail.com",
|
Domain: "hotmail.com",
|
||||||
Rules: map[string]*server.Rule{"id-existed": &server.Rule{ID: "id-existed"}},
|
Rules: map[string]*server.Rule{"id-existed": &server.Rule{ID: "id-existed"}},
|
||||||
Groups: map[string]*server.Group{
|
Groups: map[string]*server.Group{
|
||||||
"F": &server.Group{ID: "F"},
|
"F": {ID: "F"},
|
||||||
"G": &server.Group{ID: "G"},
|
"G": {ID: "G"},
|
||||||
},
|
},
|
||||||
}, nil
|
Users: map[string]*server.User{
|
||||||
|
"test_user": user,
|
||||||
|
},
|
||||||
|
}, user, nil
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
authAudience: "",
|
authAudience: "",
|
||||||
@@ -235,7 +239,7 @@ func TestRulesWriteRule(t *testing.T) {
|
|||||||
requestPath: "/api/rules/id-existed",
|
requestPath: "/api/rules/id-existed",
|
||||||
requestBody: bytes.NewBuffer(
|
requestBody: bytes.NewBuffer(
|
||||||
[]byte(`[{"op":"insert","path":"name","value":[""]}]`)),
|
[]byte(`[{"op":"insert","path":"name","value":[""]}]`)),
|
||||||
expectedStatus: http.StatusBadRequest,
|
expectedStatus: http.StatusUnprocessableEntity,
|
||||||
expectedBody: false,
|
expectedBody: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5,10 +5,9 @@ import (
|
|||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/netbirdio/netbird/management/server"
|
"github.com/netbirdio/netbird/management/server"
|
||||||
"github.com/netbirdio/netbird/management/server/http/api"
|
"github.com/netbirdio/netbird/management/server/http/api"
|
||||||
|
"github.com/netbirdio/netbird/management/server/http/util"
|
||||||
"github.com/netbirdio/netbird/management/server/jwtclaims"
|
"github.com/netbirdio/netbird/management/server/jwtclaims"
|
||||||
log "github.com/sirupsen/logrus"
|
"github.com/netbirdio/netbird/management/server/status"
|
||||||
"google.golang.org/grpc/codes"
|
|
||||||
"google.golang.org/grpc/status"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -28,145 +27,143 @@ func NewSetupKeysHandler(accountManager server.AccountManager, authAudience stri
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *SetupKeys) updateKey(accountId string, keyId string, w http.ResponseWriter, r *http.Request) {
|
// CreateSetupKeyHandler is a POST requests that creates a new SetupKey
|
||||||
req := &api.PutApiSetupKeysIdJSONRequestBody{}
|
func (h *SetupKeys) CreateSetupKeyHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
err := json.NewDecoder(r.Body).Decode(&req)
|
claims := h.jwtExtractor.ExtractClaimsFromRequestContext(r, h.authAudience)
|
||||||
|
account, _, err := h.accountManager.GetAccountFromToken(claims)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
util.WriteError(err, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var key *server.SetupKey
|
|
||||||
if req.Revoked {
|
|
||||||
//handle only if being revoked, don't allow to enable key again for now
|
|
||||||
key, err = h.accountManager.RevokeSetupKey(accountId, keyId)
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, "failed revoking key", http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(req.Name) != 0 {
|
|
||||||
key, err = h.accountManager.RenameSetupKey(accountId, keyId, req.Name)
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, "failed renaming key", http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if key != nil {
|
|
||||||
writeSuccess(w, key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *SetupKeys) getKey(accountId string, keyId string, w http.ResponseWriter, r *http.Request) {
|
|
||||||
account, err := h.accountManager.GetAccountById(accountId)
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, "account doesn't exist", http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for _, key := range account.SetupKeys {
|
|
||||||
if key.Id == keyId {
|
|
||||||
writeSuccess(w, key)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
http.Error(w, "setup key not found", http.StatusNotFound)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *SetupKeys) createKey(accountId string, w http.ResponseWriter, r *http.Request) {
|
|
||||||
req := &api.PostApiSetupKeysJSONRequestBody{}
|
req := &api.PostApiSetupKeysJSONRequestBody{}
|
||||||
err := json.NewDecoder(r.Body).Decode(&req)
|
err = json.NewDecoder(r.Body).Decode(&req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
util.WriteErrorResponse("couldn't parse JSON request", http.StatusBadRequest, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if req.Name == "" {
|
if req.Name == "" {
|
||||||
http.Error(w, "Setup key name shouldn't be empty", http.StatusUnprocessableEntity)
|
util.WriteError(status.Errorf(status.InvalidArgument, "setup key name shouldn't be empty"), w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !(server.SetupKeyType(req.Type) == server.SetupKeyReusable ||
|
if !(server.SetupKeyType(req.Type) == server.SetupKeyReusable ||
|
||||||
server.SetupKeyType(req.Type) == server.SetupKeyOneOff) {
|
server.SetupKeyType(req.Type) == server.SetupKeyOneOff) {
|
||||||
|
util.WriteError(status.Errorf(status.InvalidArgument, "unknown setup key type %s", string(req.Type)), w)
|
||||||
http.Error(w, "unknown setup key type "+string(req.Type), http.StatusBadRequest)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
expiresIn := time.Duration(req.ExpiresIn) * time.Second
|
expiresIn := time.Duration(req.ExpiresIn) * time.Second
|
||||||
|
|
||||||
setupKey, err := h.accountManager.AddSetupKey(accountId, req.Name, server.SetupKeyType(req.Type), expiresIn)
|
if req.AutoGroups == nil {
|
||||||
|
req.AutoGroups = []string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
setupKey, err := h.accountManager.CreateSetupKey(account.Id, req.Name, server.SetupKeyType(req.Type), expiresIn,
|
||||||
|
req.AutoGroups)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errStatus, ok := status.FromError(err)
|
util.WriteError(err, w)
|
||||||
if ok && errStatus.Code() == codes.NotFound {
|
|
||||||
http.Error(w, "account not found", http.StatusNotFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
http.Error(w, "failed adding setup key", http.StatusInternalServerError)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
writeSuccess(w, setupKey)
|
writeSuccess(w, setupKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *SetupKeys) HandleKey(w http.ResponseWriter, r *http.Request) {
|
// GetSetupKeyHandler is a GET request to get a SetupKey by ID
|
||||||
account, err := getJWTAccount(h.accountManager, h.jwtExtractor, h.authAudience, r)
|
func (h *SetupKeys) GetSetupKeyHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
claims := h.jwtExtractor.ExtractClaimsFromRequestContext(r, h.authAudience)
|
||||||
|
account, user, err := h.accountManager.GetAccountFromToken(claims)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err)
|
util.WriteError(err, w)
|
||||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
keyId := vars["id"]
|
keyID := vars["id"]
|
||||||
if len(keyId) == 0 {
|
if len(keyID) == 0 {
|
||||||
http.Error(w, "invalid key Id", http.StatusBadRequest)
|
util.WriteError(status.Errorf(status.InvalidArgument, "invalid key ID"), w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
switch r.Method {
|
key, err := h.accountManager.GetSetupKey(account.Id, user.Id, keyID)
|
||||||
case http.MethodPut:
|
if err != nil {
|
||||||
h.updateKey(account.Id, keyId, w, r)
|
util.WriteError(err, w)
|
||||||
return
|
return
|
||||||
case http.MethodGet:
|
|
||||||
h.getKey(account.Id, keyId, w, r)
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
http.Error(w, "", http.StatusNotFound)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
writeSuccess(w, key)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *SetupKeys) GetKeys(w http.ResponseWriter, r *http.Request) {
|
// UpdateSetupKeyHandler is a PUT request to update server.SetupKey
|
||||||
|
func (h *SetupKeys) UpdateSetupKeyHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
account, err := getJWTAccount(h.accountManager, h.jwtExtractor, h.authAudience, r)
|
claims := h.jwtExtractor.ExtractClaimsFromRequestContext(r, h.authAudience)
|
||||||
|
account, _, err := h.accountManager.GetAccountFromToken(claims)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err)
|
util.WriteError(err, w)
|
||||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
switch r.Method {
|
vars := mux.Vars(r)
|
||||||
case http.MethodPost:
|
keyID := vars["id"]
|
||||||
h.createKey(account.Id, w, r)
|
if len(keyID) == 0 {
|
||||||
|
util.WriteError(status.Errorf(status.InvalidArgument, "invalid key ID"), w)
|
||||||
return
|
return
|
||||||
case http.MethodGet:
|
|
||||||
w.WriteHeader(200)
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
|
|
||||||
respBody := []*api.SetupKey{}
|
|
||||||
for _, key := range account.SetupKeys {
|
|
||||||
respBody = append(respBody, toResponseBody(key))
|
|
||||||
}
|
|
||||||
|
|
||||||
err = json.NewEncoder(w).Encode(respBody)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("failed encoding account peers %s: %v", account.Id, err)
|
|
||||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
http.Error(w, "", http.StatusNotFound)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
req := &api.PutApiSetupKeysIdJSONRequestBody{}
|
||||||
|
err = json.NewDecoder(r.Body).Decode(&req)
|
||||||
|
if err != nil {
|
||||||
|
util.WriteErrorResponse("couldn't parse JSON request", http.StatusBadRequest, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Name == "" {
|
||||||
|
util.WriteError(status.Errorf(status.InvalidArgument, "setup key name field is invalid: %s", req.Name), w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.AutoGroups == nil {
|
||||||
|
util.WriteError(status.Errorf(status.InvalidArgument, "setup key AutoGroups field is invalid"), w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
newKey := &server.SetupKey{}
|
||||||
|
newKey.AutoGroups = req.AutoGroups
|
||||||
|
newKey.Revoked = req.Revoked
|
||||||
|
newKey.Name = req.Name
|
||||||
|
newKey.Id = keyID
|
||||||
|
|
||||||
|
newKey, err = h.accountManager.SaveSetupKey(account.Id, newKey)
|
||||||
|
if err != nil {
|
||||||
|
util.WriteError(err, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writeSuccess(w, newKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAllSetupKeysHandler is a GET request that returns a list of SetupKey
|
||||||
|
func (h *SetupKeys) GetAllSetupKeysHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
|
claims := h.jwtExtractor.ExtractClaimsFromRequestContext(r, h.authAudience)
|
||||||
|
account, user, err := h.accountManager.GetAccountFromToken(claims)
|
||||||
|
if err != nil {
|
||||||
|
util.WriteError(err, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setupKeys, err := h.accountManager.ListSetupKeys(account.Id, user.Id)
|
||||||
|
if err != nil {
|
||||||
|
util.WriteError(err, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
apiSetupKeys := make([]*api.SetupKey, 0)
|
||||||
|
for _, key := range setupKeys {
|
||||||
|
apiSetupKeys = append(apiSetupKeys, toResponseBody(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
util.WriteJSONObject(w, apiSetupKeys)
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeSuccess(w http.ResponseWriter, key *server.SetupKey) {
|
func writeSuccess(w http.ResponseWriter, key *server.SetupKey) {
|
||||||
@@ -174,7 +171,7 @@ func writeSuccess(w http.ResponseWriter, key *server.SetupKey) {
|
|||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
err := json.NewEncoder(w).Encode(toResponseBody(key))
|
err := json.NewEncoder(w).Encode(toResponseBody(key))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "failed handling request", http.StatusInternalServerError)
|
util.WriteError(err, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -190,16 +187,19 @@ func toResponseBody(key *server.SetupKey) *api.SetupKey {
|
|||||||
} else {
|
} else {
|
||||||
state = "valid"
|
state = "valid"
|
||||||
}
|
}
|
||||||
|
|
||||||
return &api.SetupKey{
|
return &api.SetupKey{
|
||||||
Id: key.Id,
|
Id: key.Id,
|
||||||
Key: key.Key,
|
Key: key.Key,
|
||||||
Name: key.Name,
|
Name: key.Name,
|
||||||
Expires: key.ExpiresAt,
|
Expires: key.ExpiresAt,
|
||||||
Type: string(key.Type),
|
Type: string(key.Type),
|
||||||
Valid: key.IsValid(),
|
Valid: key.IsValid(),
|
||||||
Revoked: key.Revoked,
|
Revoked: key.Revoked,
|
||||||
UsedTimes: key.UsedTimes,
|
UsedTimes: key.UsedTimes,
|
||||||
LastUsed: key.LastUsed,
|
LastUsed: key.LastUsed,
|
||||||
State: state,
|
State: state,
|
||||||
|
AutoGroups: key.AutoGroups,
|
||||||
|
UpdatedAt: key.UpdatedAt,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
227
management/server/http/setupkeys_test.go
Normal file
227
management/server/http/setupkeys_test.go
Normal file
@@ -0,0 +1,227 @@
|
|||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/netbirdio/netbird/management/server/http/api"
|
||||||
|
"github.com/netbirdio/netbird/management/server/status"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/management/server/jwtclaims"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/management/server"
|
||||||
|
"github.com/netbirdio/netbird/management/server/mock_server"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
existingSetupKeyID = "existingSetupKeyID"
|
||||||
|
newSetupKeyName = "New Setup Key"
|
||||||
|
updatedSetupKeyName = "KKKey"
|
||||||
|
notFoundSetupKeyID = "notFoundSetupKeyID"
|
||||||
|
)
|
||||||
|
|
||||||
|
func initSetupKeysTestMetaData(defaultKey *server.SetupKey, newKey *server.SetupKey, updatedSetupKey *server.SetupKey,
|
||||||
|
user *server.User) *SetupKeys {
|
||||||
|
return &SetupKeys{
|
||||||
|
accountManager: &mock_server.MockAccountManager{
|
||||||
|
GetAccountFromTokenFunc: func(claims jwtclaims.AuthorizationClaims) (*server.Account, *server.User, error) {
|
||||||
|
return &server.Account{
|
||||||
|
Id: testAccountID,
|
||||||
|
Domain: "hotmail.com",
|
||||||
|
Users: map[string]*server.User{
|
||||||
|
user.Id: user,
|
||||||
|
},
|
||||||
|
SetupKeys: map[string]*server.SetupKey{
|
||||||
|
defaultKey.Key: defaultKey,
|
||||||
|
},
|
||||||
|
Groups: map[string]*server.Group{
|
||||||
|
"group-1": {ID: "group-1", Peers: []string{"A", "B"}},
|
||||||
|
"id-all": {ID: "id-all", Name: "All"}},
|
||||||
|
}, user, nil
|
||||||
|
},
|
||||||
|
CreateSetupKeyFunc: func(_ string, keyName string, typ server.SetupKeyType, _ time.Duration, _ []string) (*server.SetupKey, error) {
|
||||||
|
if keyName == newKey.Name || typ != newKey.Type {
|
||||||
|
return newKey, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("failed creating setup key")
|
||||||
|
},
|
||||||
|
GetSetupKeyFunc: func(accountID, userID, keyID string) (*server.SetupKey, error) {
|
||||||
|
switch keyID {
|
||||||
|
case defaultKey.Id:
|
||||||
|
return defaultKey, nil
|
||||||
|
case newKey.Id:
|
||||||
|
return newKey, nil
|
||||||
|
default:
|
||||||
|
return nil, status.Errorf(status.NotFound, "key %s not found", keyID)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
SaveSetupKeyFunc: func(accountID string, key *server.SetupKey) (*server.SetupKey, error) {
|
||||||
|
if key.Id == updatedSetupKey.Id {
|
||||||
|
return updatedSetupKey, nil
|
||||||
|
}
|
||||||
|
return nil, status.Errorf(status.NotFound, "key %s not found", key.Id)
|
||||||
|
},
|
||||||
|
|
||||||
|
ListSetupKeysFunc: func(accountID, userID string) ([]*server.SetupKey, error) {
|
||||||
|
return []*server.SetupKey{defaultKey}, nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
authAudience: "",
|
||||||
|
jwtExtractor: jwtclaims.ClaimsExtractor{
|
||||||
|
ExtractClaimsFromRequestContext: func(r *http.Request, authAudience string) jwtclaims.AuthorizationClaims {
|
||||||
|
return jwtclaims.AuthorizationClaims{
|
||||||
|
UserId: user.Id,
|
||||||
|
Domain: "hotmail.com",
|
||||||
|
AccountId: testAccountID,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetupKeysHandlers(t *testing.T) {
|
||||||
|
defaultSetupKey := server.GenerateDefaultSetupKey()
|
||||||
|
defaultSetupKey.Id = existingSetupKeyID
|
||||||
|
|
||||||
|
adminUser := server.NewAdminUser("test_user")
|
||||||
|
|
||||||
|
newSetupKey := server.GenerateSetupKey(newSetupKeyName, server.SetupKeyReusable, 0, []string{"group-1"})
|
||||||
|
updatedDefaultSetupKey := defaultSetupKey.Copy()
|
||||||
|
updatedDefaultSetupKey.AutoGroups = []string{"group-1"}
|
||||||
|
updatedDefaultSetupKey.Name = updatedSetupKeyName
|
||||||
|
updatedDefaultSetupKey.Revoked = true
|
||||||
|
|
||||||
|
tt := []struct {
|
||||||
|
name string
|
||||||
|
requestType string
|
||||||
|
requestPath string
|
||||||
|
requestBody io.Reader
|
||||||
|
expectedStatus int
|
||||||
|
expectedBody bool
|
||||||
|
expectedSetupKey *api.SetupKey
|
||||||
|
expectedSetupKeys []*api.SetupKey
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Get Setup Keys",
|
||||||
|
requestType: http.MethodGet,
|
||||||
|
requestPath: "/api/setup-keys",
|
||||||
|
expectedStatus: http.StatusOK,
|
||||||
|
expectedBody: true,
|
||||||
|
expectedSetupKeys: []*api.SetupKey{toResponseBody(defaultSetupKey)},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Get Existing Setup Key",
|
||||||
|
requestType: http.MethodGet,
|
||||||
|
requestPath: "/api/setup-keys/" + existingSetupKeyID,
|
||||||
|
expectedStatus: http.StatusOK,
|
||||||
|
expectedBody: true,
|
||||||
|
expectedSetupKey: toResponseBody(defaultSetupKey),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Get Not Existing Setup Key",
|
||||||
|
requestType: http.MethodGet,
|
||||||
|
requestPath: "/api/setup-keys/" + notFoundSetupKeyID,
|
||||||
|
expectedStatus: http.StatusNotFound,
|
||||||
|
expectedBody: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Create Setup Key",
|
||||||
|
requestType: http.MethodPost,
|
||||||
|
requestPath: "/api/setup-keys",
|
||||||
|
requestBody: bytes.NewBuffer(
|
||||||
|
[]byte(fmt.Sprintf("{\"name\":\"%s\",\"type\":\"%s\"}", newSetupKey.Name, newSetupKey.Type))),
|
||||||
|
expectedStatus: http.StatusOK,
|
||||||
|
expectedBody: true,
|
||||||
|
expectedSetupKey: toResponseBody(newSetupKey),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Update Setup Key",
|
||||||
|
requestType: http.MethodPut,
|
||||||
|
requestPath: "/api/setup-keys/" + defaultSetupKey.Id,
|
||||||
|
requestBody: bytes.NewBuffer(
|
||||||
|
[]byte(fmt.Sprintf("{\"name\":\"%s\",\"auto_groups\":[\"%s\"], \"revoked\":%v}",
|
||||||
|
updatedDefaultSetupKey.Type,
|
||||||
|
updatedDefaultSetupKey.AutoGroups[0],
|
||||||
|
updatedDefaultSetupKey.Revoked,
|
||||||
|
))),
|
||||||
|
expectedStatus: http.StatusOK,
|
||||||
|
expectedBody: true,
|
||||||
|
expectedSetupKey: toResponseBody(updatedDefaultSetupKey),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
handler := initSetupKeysTestMetaData(defaultSetupKey, newSetupKey, updatedDefaultSetupKey, adminUser)
|
||||||
|
|
||||||
|
for _, tc := range tt {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
recorder := httptest.NewRecorder()
|
||||||
|
req := httptest.NewRequest(tc.requestType, tc.requestPath, tc.requestBody)
|
||||||
|
|
||||||
|
router := mux.NewRouter()
|
||||||
|
router.HandleFunc("/api/setup-keys", handler.GetAllSetupKeysHandler).Methods("GET", "OPTIONS")
|
||||||
|
router.HandleFunc("/api/setup-keys", handler.CreateSetupKeyHandler).Methods("POST", "OPTIONS")
|
||||||
|
router.HandleFunc("/api/setup-keys/{id}", handler.GetSetupKeyHandler).Methods("GET", "OPTIONS")
|
||||||
|
router.HandleFunc("/api/setup-keys/{id}", handler.UpdateSetupKeyHandler).Methods("PUT", "OPTIONS")
|
||||||
|
router.ServeHTTP(recorder, req)
|
||||||
|
|
||||||
|
res := recorder.Result()
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
content, err := io.ReadAll(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("I don't know what I expected; %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if status := recorder.Code; status != tc.expectedStatus {
|
||||||
|
t.Errorf("handler returned wrong status code: got %v want %v, content: %s",
|
||||||
|
status, tc.expectedStatus, string(content))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !tc.expectedBody {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.expectedSetupKey != nil {
|
||||||
|
got := &api.SetupKey{}
|
||||||
|
if err = json.Unmarshal(content, &got); err != nil {
|
||||||
|
t.Fatalf("Sent content is not in correct json format; %v", err)
|
||||||
|
}
|
||||||
|
assertKeys(t, got, tc.expectedSetupKey)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(tc.expectedSetupKeys) > 0 {
|
||||||
|
var got []*api.SetupKey
|
||||||
|
if err = json.Unmarshal(content, &got); err != nil {
|
||||||
|
t.Fatalf("Sent content is not in correct json format; %v", err)
|
||||||
|
}
|
||||||
|
assertKeys(t, got[0], tc.expectedSetupKeys[0])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertKeys(t *testing.T, got *api.SetupKey, expected *api.SetupKey) {
|
||||||
|
// this comparison is done manually because when converting to JSON dates formatted differently
|
||||||
|
// assert.Equal(t, got.UpdatedAt, tc.expectedSetupKey.UpdatedAt) //doesn't work
|
||||||
|
assert.WithinDurationf(t, got.UpdatedAt, expected.UpdatedAt, 0, "")
|
||||||
|
assert.WithinDurationf(t, got.Expires, expected.Expires, 0, "")
|
||||||
|
assert.Equal(t, got.Name, expected.Name)
|
||||||
|
assert.Equal(t, got.Id, expected.Id)
|
||||||
|
assert.Equal(t, got.Key, expected.Key)
|
||||||
|
assert.Equal(t, got.Type, expected.Type)
|
||||||
|
assert.Equal(t, got.UsedTimes, expected.UsedTimes)
|
||||||
|
assert.Equal(t, got.Revoked, expected.Revoked)
|
||||||
|
assert.ElementsMatch(t, got.AutoGroups, expected.AutoGroups)
|
||||||
|
}
|
||||||
@@ -1,11 +1,13 @@
|
|||||||
package http
|
package http
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
"github.com/netbirdio/netbird/management/server/http/api"
|
"github.com/netbirdio/netbird/management/server/http/api"
|
||||||
|
"github.com/netbirdio/netbird/management/server/http/util"
|
||||||
|
"github.com/netbirdio/netbird/management/server/status"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/management/server"
|
"github.com/netbirdio/netbird/management/server"
|
||||||
"github.com/netbirdio/netbird/management/server/jwtclaims"
|
"github.com/netbirdio/netbird/management/server/jwtclaims"
|
||||||
)
|
)
|
||||||
@@ -24,38 +26,144 @@ func NewUserHandler(accountManager server.AccountManager, authAudience string) *
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateUser is a PUT requests to update User data
|
||||||
|
func (h *UserHandler) UpdateUser(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodPut {
|
||||||
|
util.WriteErrorResponse("wrong HTTP method", http.StatusMethodNotAllowed, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
claims := h.jwtExtractor.ExtractClaimsFromRequestContext(r, h.authAudience)
|
||||||
|
account, _, err := h.accountManager.GetAccountFromToken(claims)
|
||||||
|
if err != nil {
|
||||||
|
util.WriteError(err, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
userID := vars["id"]
|
||||||
|
if len(userID) == 0 {
|
||||||
|
util.WriteError(status.Errorf(status.InvalidArgument, "invalid user ID"), w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req := &api.PutApiUsersIdJSONRequestBody{}
|
||||||
|
err = json.NewDecoder(r.Body).Decode(&req)
|
||||||
|
if err != nil {
|
||||||
|
util.WriteErrorResponse("couldn't parse JSON request", http.StatusBadRequest, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
userRole := server.StrRoleToUserRole(req.Role)
|
||||||
|
if userRole == server.UserRoleUnknown {
|
||||||
|
util.WriteError(status.Errorf(status.InvalidArgument, "invalid user role"), w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
newUser, err := h.accountManager.SaveUser(account.Id, &server.User{
|
||||||
|
Id: userID,
|
||||||
|
Role: userRole,
|
||||||
|
AutoGroups: req.AutoGroups,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
util.WriteError(err, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
util.WriteJSONObject(w, toUserResponse(newUser))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateUserHandler creates a User in the system with a status "invited" (effectively this is a user invite).
|
||||||
|
func (h *UserHandler) CreateUserHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodPost {
|
||||||
|
util.WriteErrorResponse("wrong HTTP method", http.StatusMethodNotAllowed, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
claims := h.jwtExtractor.ExtractClaimsFromRequestContext(r, h.authAudience)
|
||||||
|
account, _, err := h.accountManager.GetAccountFromToken(claims)
|
||||||
|
if err != nil {
|
||||||
|
util.WriteError(err, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req := &api.PostApiUsersJSONRequestBody{}
|
||||||
|
err = json.NewDecoder(r.Body).Decode(&req)
|
||||||
|
if err != nil {
|
||||||
|
util.WriteErrorResponse("couldn't parse JSON request", http.StatusBadRequest, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if server.StrRoleToUserRole(req.Role) == server.UserRoleUnknown {
|
||||||
|
util.WriteError(status.Errorf(status.InvalidArgument, "unknown user role %s", req.Role), w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
newUser, err := h.accountManager.CreateUser(account.Id, &server.UserInfo{
|
||||||
|
Email: req.Email,
|
||||||
|
Name: *req.Name,
|
||||||
|
Role: req.Role,
|
||||||
|
AutoGroups: req.AutoGroups,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
util.WriteError(err, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
util.WriteJSONObject(w, toUserResponse(newUser))
|
||||||
|
}
|
||||||
|
|
||||||
// GetUsers returns a list of users of the account this user belongs to.
|
// GetUsers returns a list of users of the account this user belongs to.
|
||||||
// It also gathers additional user data (like email and name) from the IDP manager.
|
// It also gathers additional user data (like email and name) from the IDP manager.
|
||||||
func (h *UserHandler) GetUsers(w http.ResponseWriter, r *http.Request) {
|
func (h *UserHandler) GetUsers(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodGet {
|
if r.Method != http.MethodGet {
|
||||||
http.Error(w, "", http.StatusBadRequest)
|
util.WriteErrorResponse("wrong HTTP method", http.StatusMethodNotAllowed, w)
|
||||||
}
|
|
||||||
|
|
||||||
account, err := getJWTAccount(h.accountManager, h.jwtExtractor, h.authAudience, r)
|
|
||||||
if err != nil {
|
|
||||||
log.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := h.accountManager.GetUsersFromAccount(account.Id)
|
|
||||||
if err != nil {
|
|
||||||
log.Error(err)
|
|
||||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
users := []*api.User{}
|
claims := h.jwtExtractor.ExtractClaimsFromRequestContext(r, h.authAudience)
|
||||||
|
account, user, err := h.accountManager.GetAccountFromToken(claims)
|
||||||
|
if err != nil {
|
||||||
|
util.WriteError(err, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := h.accountManager.GetUsersFromAccount(account.Id, user.Id)
|
||||||
|
if err != nil {
|
||||||
|
util.WriteError(err, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
users := make([]*api.User, 0)
|
||||||
for _, r := range data {
|
for _, r := range data {
|
||||||
users = append(users, toUserResponse(r))
|
users = append(users, toUserResponse(r))
|
||||||
}
|
}
|
||||||
|
|
||||||
writeJSONObject(w, users)
|
util.WriteJSONObject(w, users)
|
||||||
}
|
}
|
||||||
|
|
||||||
func toUserResponse(user *server.UserInfo) *api.User {
|
func toUserResponse(user *server.UserInfo) *api.User {
|
||||||
|
|
||||||
|
autoGroups := user.AutoGroups
|
||||||
|
if autoGroups == nil {
|
||||||
|
autoGroups = []string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var userStatus api.UserStatus
|
||||||
|
switch user.Status {
|
||||||
|
case "active":
|
||||||
|
userStatus = api.UserStatusActive
|
||||||
|
case "invited":
|
||||||
|
userStatus = api.UserStatusInvited
|
||||||
|
default:
|
||||||
|
userStatus = api.UserStatusDisabled
|
||||||
|
}
|
||||||
|
|
||||||
return &api.User{
|
return &api.User{
|
||||||
Id: user.ID,
|
Id: user.ID,
|
||||||
Name: user.Name,
|
Name: user.Name,
|
||||||
Email: user.Email,
|
Email: user.Email,
|
||||||
Role: user.Role,
|
Role: user.Role,
|
||||||
|
AutoGroups: autoGroups,
|
||||||
|
Status: userStatus,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import (
|
|||||||
func initUsers(user ...*server.User) *UserHandler {
|
func initUsers(user ...*server.User) *UserHandler {
|
||||||
return &UserHandler{
|
return &UserHandler{
|
||||||
accountManager: &mock_server.MockAccountManager{
|
accountManager: &mock_server.MockAccountManager{
|
||||||
GetAccountWithAuthorizationClaimsFunc: func(claims jwtclaims.AuthorizationClaims) (*server.Account, error) {
|
GetAccountFromTokenFunc: func(claims jwtclaims.AuthorizationClaims) (*server.Account, *server.User, error) {
|
||||||
users := make(map[string]*server.User, 0)
|
users := make(map[string]*server.User, 0)
|
||||||
for _, u := range user {
|
for _, u := range user {
|
||||||
users[u.Id] = u
|
users[u.Id] = u
|
||||||
@@ -25,9 +25,9 @@ func initUsers(user ...*server.User) *UserHandler {
|
|||||||
Id: "12345",
|
Id: "12345",
|
||||||
Domain: "netbird.io",
|
Domain: "netbird.io",
|
||||||
Users: users,
|
Users: users,
|
||||||
}, nil
|
}, users[claims.UserId], nil
|
||||||
},
|
},
|
||||||
GetUsersFromAccountFunc: func(accountID string) ([]*server.UserInfo, error) {
|
GetUsersFromAccountFunc: func(accountID, userID string) ([]*server.UserInfo, error) {
|
||||||
users := make([]*server.UserInfo, 0)
|
users := make([]*server.UserInfo, 0)
|
||||||
for _, v := range user {
|
for _, v := range user {
|
||||||
users = append(users, &server.UserInfo{
|
users = append(users, &server.UserInfo{
|
||||||
@@ -44,7 +44,7 @@ func initUsers(user ...*server.User) *UserHandler {
|
|||||||
jwtExtractor: jwtclaims.ClaimsExtractor{
|
jwtExtractor: jwtclaims.ClaimsExtractor{
|
||||||
ExtractClaimsFromRequestContext: func(r *http.Request, authAudiance string) jwtclaims.AuthorizationClaims {
|
ExtractClaimsFromRequestContext: func(r *http.Request, authAudiance string) jwtclaims.AuthorizationClaims {
|
||||||
return jwtclaims.AuthorizationClaims{
|
return jwtclaims.AuthorizationClaims{
|
||||||
UserId: "test_user",
|
UserId: "1",
|
||||||
Domain: "hotmail.com",
|
Domain: "hotmail.com",
|
||||||
AccountId: "test_id",
|
AccountId: "test_id",
|
||||||
}
|
}
|
||||||
@@ -66,7 +66,6 @@ func TestGetUsers(t *testing.T) {
|
|||||||
expectedResult []*server.User
|
expectedResult []*server.User
|
||||||
}{
|
}{
|
||||||
{name: "GetAllUsers", requestType: http.MethodGet, requestPath: "/api/users/", expectedStatus: http.StatusOK, expectedResult: users},
|
{name: "GetAllUsers", requestType: http.MethodGet, requestPath: "/api/users/", expectedStatus: http.StatusOK, expectedResult: users},
|
||||||
{name: "WrongRequestMethod", requestType: http.MethodPost, requestPath: "/api/users/", expectedStatus: http.StatusBadRequest},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range tt {
|
for _, tc := range tt {
|
||||||
|
|||||||
@@ -1,66 +0,0 @@
|
|||||||
package http
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"github.com/netbirdio/netbird/management/server"
|
|
||||||
"github.com/netbirdio/netbird/management/server/jwtclaims"
|
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
//writeJSONObject simply writes object to the HTTP reponse in JSON format
|
|
||||||
func writeJSONObject(w http.ResponseWriter, obj interface{}) {
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
|
|
||||||
err := json.NewEncoder(w).Encode(obj)
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, "failed handling request", http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Duration is used strictly for JSON requests/responses due to duration marshalling issues
|
|
||||||
type Duration struct {
|
|
||||||
time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d Duration) MarshalJSON() ([]byte, error) {
|
|
||||||
return json.Marshal(d.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Duration) UnmarshalJSON(b []byte) error {
|
|
||||||
var v interface{}
|
|
||||||
if err := json.Unmarshal(b, &v); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
switch value := v.(type) {
|
|
||||||
case float64:
|
|
||||||
d.Duration = time.Duration(value)
|
|
||||||
return nil
|
|
||||||
case string:
|
|
||||||
var err error
|
|
||||||
d.Duration, err = time.ParseDuration(value)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
default:
|
|
||||||
return errors.New("invalid duration")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getJWTAccount(accountManager server.AccountManager,
|
|
||||||
jwtExtractor jwtclaims.ClaimsExtractor,
|
|
||||||
authAudience string, r *http.Request) (*server.Account, error) {
|
|
||||||
|
|
||||||
jwtClaims := jwtExtractor.ExtractClaimsFromRequestContext(r, authAudience)
|
|
||||||
|
|
||||||
account, err := accountManager.GetAccountWithAuthorizationClaims(jwtClaims)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed getting account of a user %s: %v", jwtClaims.UserId, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return account, nil
|
|
||||||
}
|
|
||||||
105
management/server/http/util/util.go
Normal file
105
management/server/http/util/util.go
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/netbirdio/netbird/management/server/status"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// WriteJSONObject simply writes object to the HTTP reponse in JSON format
|
||||||
|
func WriteJSONObject(w http.ResponseWriter, obj interface{}) {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
|
||||||
|
err := json.NewEncoder(w).Encode(obj)
|
||||||
|
if err != nil {
|
||||||
|
WriteError(err, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Duration is used strictly for JSON requests/responses due to duration marshalling issues
|
||||||
|
type Duration struct {
|
||||||
|
time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON marshals the duration
|
||||||
|
func (d Duration) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(d.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON unmarshals the duration
|
||||||
|
func (d *Duration) UnmarshalJSON(b []byte) error {
|
||||||
|
var v interface{}
|
||||||
|
if err := json.Unmarshal(b, &v); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch value := v.(type) {
|
||||||
|
case float64:
|
||||||
|
d.Duration = time.Duration(value)
|
||||||
|
return nil
|
||||||
|
case string:
|
||||||
|
var err error
|
||||||
|
d.Duration, err = time.ParseDuration(value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return errors.New("invalid duration")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteErrorResponse prepares and writes an error response i nJSON
|
||||||
|
func WriteErrorResponse(errMsg string, httpStatus int, w http.ResponseWriter) {
|
||||||
|
type errorResponse struct {
|
||||||
|
Message string `json:"message"`
|
||||||
|
Code int `json:"code"`
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(httpStatus)
|
||||||
|
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
|
||||||
|
err := json.NewEncoder(w).Encode(&errorResponse{
|
||||||
|
Message: errMsg,
|
||||||
|
Code: httpStatus,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "failed handling request", http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteError converts an error to an JSON error response.
|
||||||
|
// If it is known internal error of type server.Error then it sets the messages from the error, a generic message otherwise
|
||||||
|
func WriteError(err error, w http.ResponseWriter) {
|
||||||
|
errStatus, ok := status.FromError(err)
|
||||||
|
httpStatus := http.StatusInternalServerError
|
||||||
|
msg := "internal server error"
|
||||||
|
if ok {
|
||||||
|
switch errStatus.Type() {
|
||||||
|
case status.UserAlreadyExists:
|
||||||
|
httpStatus = http.StatusConflict
|
||||||
|
case status.AlreadyExists:
|
||||||
|
httpStatus = http.StatusConflict
|
||||||
|
case status.PreconditionFailed:
|
||||||
|
httpStatus = http.StatusPreconditionFailed
|
||||||
|
case status.PermissionDenied:
|
||||||
|
httpStatus = http.StatusForbidden
|
||||||
|
case status.NotFound:
|
||||||
|
httpStatus = http.StatusNotFound
|
||||||
|
case status.Internal:
|
||||||
|
httpStatus = http.StatusInternalServerError
|
||||||
|
case status.InvalidArgument:
|
||||||
|
httpStatus = http.StatusUnprocessableEntity
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
msg = err.Error()
|
||||||
|
} else {
|
||||||
|
unhandledMSG := fmt.Sprintf("got unhandled error code, error: %s", err.Error())
|
||||||
|
log.Error(unhandledMSG)
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteErrorResponse(msg, httpStatus, w)
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user