mirror of
https://github.com/netbirdio/netbird.git
synced 2026-05-02 15:16:38 +00:00
Compare commits
64 Commits
feature/in
...
v0.10.9
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
42
README.md
42
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).
|
||||||
@@ -105,5 +103,5 @@ See a complete [architecture overview](https://netbird.io/docs/overview/architec
|
|||||||
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.
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -80,7 +80,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 +263,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 +274,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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -107,7 +107,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)
|
||||||
|
|||||||
56
client/internal/dns/local.go
Normal file
56
client/internal/dns/local.go
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"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.Tracef("received question: %#v\n", r.Question[0])
|
||||||
|
response := d.lookupRecord(r)
|
||||||
|
if response == nil {
|
||||||
|
log.Debugf("got empty response for question: %#v\n", r.Question[0])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
replyMessage := &dns.Msg{}
|
||||||
|
replyMessage.SetReply(r)
|
||||||
|
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 {
|
||||||
|
record, found := d.records.Load(r.Question[0].Name)
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
d.records.Store(fullRecord.Header().Name, fullRecord)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *localResolver) deleteRecord(recordKey string) {
|
||||||
|
d.records.Delete(dns.Fqdn(recordKey))
|
||||||
|
}
|
||||||
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 {
|
||||||
|
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() {}
|
||||||
277
client/internal/dns/server.go
Normal file
277
client/internal/dns/server.go
Normal file
@@ -0,0 +1,277 @@
|
|||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
nbdns "github.com/netbirdio/netbird/dns"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
port = 5053
|
||||||
|
defaultIP = "0.0.0.0"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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
|
||||||
|
updateSerial uint64
|
||||||
|
listenerIsRunning bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type registrationMap map[string]struct{}
|
||||||
|
|
||||||
|
type muxUpdate struct {
|
||||||
|
domain string
|
||||||
|
handler dns.Handler
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDefaultServer returns a new dns server
|
||||||
|
func NewDefaultServer(ctx context.Context) *DefaultServer {
|
||||||
|
mux := dns.NewServeMux()
|
||||||
|
|
||||||
|
dnsServer := &dns.Server{
|
||||||
|
Addr: fmt.Sprintf("%s:%d", defaultIP, port),
|
||||||
|
Net: "udp",
|
||||||
|
Handler: mux,
|
||||||
|
UDPSize: 65535,
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, stop := context.WithCancel(ctx)
|
||||||
|
|
||||||
|
return &DefaultServer{
|
||||||
|
ctx: ctx,
|
||||||
|
stop: stop,
|
||||||
|
server: dnsServer,
|
||||||
|
dnsMux: mux,
|
||||||
|
dnsMuxMap: make(registrationMap),
|
||||||
|
localResolver: &localResolver{
|
||||||
|
registeredMap: make(registrationMap),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start runs the listener in a go routine
|
||||||
|
func (s *DefaultServer) Start() {
|
||||||
|
log.Debugf("starting dns on %s:%d", defaultIP, port)
|
||||||
|
go func() {
|
||||||
|
s.setListenerStatus(true)
|
||||||
|
defer s.setListenerStatus(false)
|
||||||
|
err := s.server.ListenAndServe()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("dns server returned an error: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DefaultServer) setListenerStatus(running bool) {
|
||||||
|
s.listenerIsRunning = running
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop stops the server
|
||||||
|
func (s *DefaultServer) Stop() {
|
||||||
|
s.stop()
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
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 {
|
||||||
|
localRecords[record.Name] = 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)
|
||||||
|
}
|
||||||
285
client/internal/dns/server_test.go
Normal file
285
client/internal/dns/server_test.go
Normal file
@@ -0,0 +1,285 @@
|
|||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
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{zoneRecords[0].Name: struct{}{}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "New Config Should Succeed",
|
||||||
|
initLocalMap: registrationMap{"netbird.cloud": struct{}{}},
|
||||||
|
initUpstreamMap: registrationMap{zoneRecords[0].Name: 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{zoneRecords[0].Name: 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) {
|
||||||
|
ctx := context.Background()
|
||||||
|
dnsServer := NewDefaultServer(ctx)
|
||||||
|
|
||||||
|
dnsServer.dnsMuxMap = testCase.initUpstreamMap
|
||||||
|
dnsServer.localResolver.registeredMap = testCase.initLocalMap
|
||||||
|
dnsServer.updateSerial = testCase.initSerial
|
||||||
|
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) {
|
||||||
|
ctx := context.Background()
|
||||||
|
dnsServer := NewDefaultServer(ctx)
|
||||||
|
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.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(ctx, 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")
|
||||||
|
}
|
||||||
|
}
|
||||||
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.Tracef("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,12 +3,15 @@ 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"
|
||||||
nbssh "github.com/netbirdio/netbird/client/ssh"
|
nbssh "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/route"
|
"github.com/netbirdio/netbird/route"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
|
"net/netip"
|
||||||
"reflect"
|
"reflect"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -103,6 +106,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
|
||||||
@@ -130,6 +135,7 @@ func NewEngine(
|
|||||||
networkSerial: 0,
|
networkSerial: 0,
|
||||||
sshServerFunc: nbssh.DefaultSSHServer,
|
sshServerFunc: nbssh.DefaultSSHServer,
|
||||||
statusRecorder: statusRecorder,
|
statusRecorder: statusRecorder,
|
||||||
|
dnsServer: dns.NewDefaultServer(ctx),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,6 +196,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
|
||||||
@@ -638,6 +648,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 +679,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 {
|
||||||
|
|||||||
@@ -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"
|
||||||
@@ -440,7 +442,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 +488,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,
|
||||||
@@ -566,6 +568,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)
|
||||||
|
|
||||||
@@ -756,17 +935,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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
84
dns/dns.go
Normal file
84
dns/dns.go
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
// 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"
|
||||||
|
"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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
45
go.mod
45
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,23 @@ 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/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 +71,13 @@ 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/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
|
||||||
|
|||||||
88
go.sum
88
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=
|
||||||
@@ -280,8 +283,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 +347,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 +457,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 +480,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 +513,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 +526,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 +550,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 +562,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 +618,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 +626,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 +638,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 +657,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 +686,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 +699,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 +798,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 +830,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 +935,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 +1184,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 +1216,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=
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ 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)
|
// WireguardModuleIsLoaded check if we can load wireguard mod (linux only)
|
||||||
func WireguardModExists() bool {
|
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,11 @@ 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
|
||||||
if err != nil {
|
|
||||||
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 +218,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 +234,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 +251,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 +274,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 +296,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 +382,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 +448,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 +465,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 +496,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,50 +29,47 @@ 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))
|
util.WriteJSONObject(w, toPeerResponse(peer, account))
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,11 +81,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))
|
||||||
return
|
return
|
||||||
|
|
||||||
default:
|
default:
|
||||||
http.Error(w, "", http.StatusNotFound)
|
util.WriteError(status.Errorf(status.NotFound, "unknown METHOD"), w)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -95,21 +93,27 @@ 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
|
||||||
|
}
|
||||||
|
|
||||||
|
peers, err := h.accountManager.GetPeers(account.Id, user.Id)
|
||||||
|
if err != nil {
|
||||||
|
util.WriteError(err, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
respBody := []*api.Peer{}
|
respBody := []*api.Peer{}
|
||||||
for _, peer := range account.Peers {
|
for _, peer := range peers {
|
||||||
respBody = append(respBody, toPeerResponse(peer, account))
|
respBody = append(respBody, toPeerResponse(peer, account))
|
||||||
}
|
}
|
||||||
writeJSONObject(w, respBody)
|
util.WriteJSONObject(w, respBody)
|
||||||
return
|
return
|
||||||
default:
|
default:
|
||||||
http.Error(w, "", http.StatusNotFound)
|
util.WriteError(status.Errorf(status.NotFound, "unknown METHOD"), w)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,5 +148,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: peer.DNSLabel,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -6,8 +6,8 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/netbirdio/netbird/management/server/telemetry"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -25,6 +25,7 @@ type Auth0Manager struct {
|
|||||||
httpClient ManagerHTTPClient
|
httpClient ManagerHTTPClient
|
||||||
credentials ManagerCredentials
|
credentials ManagerCredentials
|
||||||
helper ManagerHelper
|
helper ManagerHelper
|
||||||
|
appMetrics telemetry.AppMetrics
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auth0ClientConfig auth0 manager client configurations
|
// Auth0ClientConfig auth0 manager client configurations
|
||||||
@@ -52,6 +53,17 @@ type Auth0Credentials struct {
|
|||||||
httpClient ManagerHTTPClient
|
httpClient ManagerHTTPClient
|
||||||
jwtToken JWTToken
|
jwtToken JWTToken
|
||||||
mux sync.Mutex
|
mux sync.Mutex
|
||||||
|
appMetrics telemetry.AppMetrics
|
||||||
|
}
|
||||||
|
|
||||||
|
// createUserRequest is a user create request
|
||||||
|
type createUserRequest struct {
|
||||||
|
Email string `json:"email"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
AppMeta AppMetadata `json:"app_metadata"`
|
||||||
|
Connection string `json:"connection"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
VerifyEmail bool `json:"verify_email"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// userExportJobRequest is a user export request struct
|
// userExportJobRequest is a user export request struct
|
||||||
@@ -87,16 +99,17 @@ type userExportJobStatusResponse struct {
|
|||||||
|
|
||||||
// auth0Profile represents an Auth0 user profile response
|
// auth0Profile represents an Auth0 user profile response
|
||||||
type auth0Profile struct {
|
type auth0Profile struct {
|
||||||
AccountID string `json:"wt_account_id"`
|
AccountID string `json:"wt_account_id"`
|
||||||
UserID string `json:"user_id"`
|
PendingInvite bool `json:"wt_pending_invite"`
|
||||||
Name string `json:"name"`
|
UserID string `json:"user_id"`
|
||||||
Email string `json:"email"`
|
Name string `json:"name"`
|
||||||
CreatedAt string `json:"created_at"`
|
Email string `json:"email"`
|
||||||
LastLogin string `json:"last_login"`
|
CreatedAt string `json:"created_at"`
|
||||||
|
LastLogin string `json:"last_login"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAuth0Manager creates a new instance of the Auth0Manager
|
// NewAuth0Manager creates a new instance of the Auth0Manager
|
||||||
func NewAuth0Manager(config Auth0ClientConfig) (*Auth0Manager, error) {
|
func NewAuth0Manager(config Auth0ClientConfig, appMetrics telemetry.AppMetrics) (*Auth0Manager, error) {
|
||||||
|
|
||||||
httpTransport := http.DefaultTransport.(*http.Transport).Clone()
|
httpTransport := http.DefaultTransport.(*http.Transport).Clone()
|
||||||
httpTransport.MaxIdleConns = 5
|
httpTransport.MaxIdleConns = 5
|
||||||
@@ -124,12 +137,15 @@ func NewAuth0Manager(config Auth0ClientConfig) (*Auth0Manager, error) {
|
|||||||
clientConfig: config,
|
clientConfig: config,
|
||||||
httpClient: httpClient,
|
httpClient: httpClient,
|
||||||
helper: helper,
|
helper: helper,
|
||||||
|
appMetrics: appMetrics,
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Auth0Manager{
|
return &Auth0Manager{
|
||||||
authIssuer: config.AuthIssuer,
|
authIssuer: config.AuthIssuer,
|
||||||
credentials: credentials,
|
credentials: credentials,
|
||||||
httpClient: httpClient,
|
httpClient: httpClient,
|
||||||
helper: helper,
|
helper: helper,
|
||||||
|
appMetrics: appMetrics,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,6 +176,9 @@ func (c *Auth0Credentials) requestJWTToken() (*http.Response, error) {
|
|||||||
|
|
||||||
res, err = c.httpClient.Do(req)
|
res, err = c.httpClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if c.appMetrics != nil {
|
||||||
|
c.appMetrics.IDPMetrics().CountRequestError()
|
||||||
|
}
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,7 +191,7 @@ func (c *Auth0Credentials) requestJWTToken() (*http.Response, error) {
|
|||||||
// parseRequestJWTResponse parses jwt raw response body and extracts token and expires in seconds
|
// parseRequestJWTResponse parses jwt raw response body and extracts token and expires in seconds
|
||||||
func (c *Auth0Credentials) parseRequestJWTResponse(rawBody io.ReadCloser) (JWTToken, error) {
|
func (c *Auth0Credentials) parseRequestJWTResponse(rawBody io.ReadCloser) (JWTToken, error) {
|
||||||
jwtToken := JWTToken{}
|
jwtToken := JWTToken{}
|
||||||
body, err := ioutil.ReadAll(rawBody)
|
body, err := io.ReadAll(rawBody)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return jwtToken, err
|
return jwtToken, err
|
||||||
}
|
}
|
||||||
@@ -204,6 +223,10 @@ func (c *Auth0Credentials) Authenticate() (JWTToken, error) {
|
|||||||
c.mux.Lock()
|
c.mux.Lock()
|
||||||
defer c.mux.Unlock()
|
defer c.mux.Unlock()
|
||||||
|
|
||||||
|
if c.appMetrics != nil {
|
||||||
|
c.appMetrics.IDPMetrics().CountAuthenticate()
|
||||||
|
}
|
||||||
|
|
||||||
// If jwtToken has an expires time and we have enough time to do a request return immediately
|
// If jwtToken has an expires time and we have enough time to do a request return immediately
|
||||||
if c.jwtStillValid() {
|
if c.jwtStillValid() {
|
||||||
return c.jwtToken, nil
|
return c.jwtToken, nil
|
||||||
@@ -230,7 +253,7 @@ func (c *Auth0Credentials) Authenticate() (JWTToken, error) {
|
|||||||
return c.jwtToken, nil
|
return c.jwtToken, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func batchRequestUsersURL(authIssuer, accountID string, page int) (string, url.Values, error) {
|
func batchRequestUsersURL(authIssuer, accountID string, page int, perPage int) (string, url.Values, error) {
|
||||||
u, err := url.Parse(authIssuer + "/api/v2/users")
|
u, err := url.Parse(authIssuer + "/api/v2/users")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil, err
|
return "", nil, err
|
||||||
@@ -238,6 +261,7 @@ func batchRequestUsersURL(authIssuer, accountID string, page int) (string, url.V
|
|||||||
q := u.Query()
|
q := u.Query()
|
||||||
q.Set("page", strconv.Itoa(page))
|
q.Set("page", strconv.Itoa(page))
|
||||||
q.Set("search_engine", "v3")
|
q.Set("search_engine", "v3")
|
||||||
|
q.Set("per_page", strconv.Itoa(perPage))
|
||||||
q.Set("q", "app_metadata.wt_account_id:"+accountID)
|
q.Set("q", "app_metadata.wt_account_id:"+accountID)
|
||||||
u.RawQuery = q.Encode()
|
u.RawQuery = q.Encode()
|
||||||
|
|
||||||
@@ -259,8 +283,9 @@ func (am *Auth0Manager) GetAccount(accountID string) ([]*UserData, error) {
|
|||||||
|
|
||||||
// https://auth0.com/docs/manage-users/user-search/retrieve-users-with-get-users-endpoint#limitations
|
// https://auth0.com/docs/manage-users/user-search/retrieve-users-with-get-users-endpoint#limitations
|
||||||
// auth0 limitation of 1000 users via this endpoint
|
// auth0 limitation of 1000 users via this endpoint
|
||||||
|
resultsPerPage := 50
|
||||||
for page := 0; page < 20; page++ {
|
for page := 0; page < 20; page++ {
|
||||||
reqURL, query, err := batchRequestUsersURL(am.authIssuer, accountID, page)
|
reqURL, query, err := batchRequestUsersURL(am.authIssuer, accountID, page, resultsPerPage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -275,38 +300,46 @@ func (am *Auth0Manager) GetAccount(accountID string) ([]*UserData, error) {
|
|||||||
|
|
||||||
res, err := am.httpClient.Do(req)
|
res, err := am.httpClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if am.appMetrics != nil {
|
||||||
|
am.appMetrics.IDPMetrics().CountRequestError()
|
||||||
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if am.appMetrics != nil {
|
||||||
|
am.appMetrics.IDPMetrics().CountGetAccount()
|
||||||
|
}
|
||||||
|
|
||||||
body, err := io.ReadAll(res.Body)
|
body, err := io.ReadAll(res.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if res.StatusCode != 200 {
|
||||||
|
return nil, fmt.Errorf("failed requesting user data from IdP %s", string(body))
|
||||||
|
}
|
||||||
|
|
||||||
var batch []UserData
|
var batch []UserData
|
||||||
err = json.Unmarshal(body, &batch)
|
err = json.Unmarshal(body, &batch)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("requested batch; %v", batch)
|
log.Debugf("returned user batch for accountID %s on page %d, %v", accountID, page, batch)
|
||||||
|
|
||||||
err = res.Body.Close()
|
err = res.Body.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if res.StatusCode != 200 {
|
|
||||||
return nil, fmt.Errorf("unable to request UserData from auth0, statusCode %d", res.StatusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(batch) == 0 {
|
|
||||||
return list, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
for user := range batch {
|
for user := range batch {
|
||||||
list = append(list, &batch[user])
|
list = append(list, &batch[user])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(batch) == 0 || len(batch) < resultsPerPage {
|
||||||
|
log.Debugf("finished loading users for accountID %s", accountID)
|
||||||
|
return list, nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return list, nil
|
return list, nil
|
||||||
@@ -329,9 +362,16 @@ func (am *Auth0Manager) GetUserDataByID(userID string, appMetadata AppMetadata)
|
|||||||
|
|
||||||
res, err := am.httpClient.Do(req)
|
res, err := am.httpClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if am.appMetrics != nil {
|
||||||
|
am.appMetrics.IDPMetrics().CountRequestError()
|
||||||
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if am.appMetrics != nil {
|
||||||
|
am.appMetrics.IDPMetrics().CountGetUserDataByID()
|
||||||
|
}
|
||||||
|
|
||||||
body, err := io.ReadAll(res.Body)
|
body, err := io.ReadAll(res.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -367,14 +407,12 @@ func (am *Auth0Manager) UpdateUserAppMetadata(userID string, appMetadata AppMeta
|
|||||||
|
|
||||||
reqURL := am.authIssuer + "/api/v2/users/" + userID
|
reqURL := am.authIssuer + "/api/v2/users/" + userID
|
||||||
|
|
||||||
data, err := am.helper.Marshal(appMetadata)
|
data, err := am.helper.Marshal(map[string]any{"app_metadata": appMetadata})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
payloadString := fmt.Sprintf("{\"app_metadata\": %s}", string(data))
|
payload := strings.NewReader(string(data))
|
||||||
|
|
||||||
payload := strings.NewReader(payloadString)
|
|
||||||
|
|
||||||
req, err := http.NewRequest("PATCH", reqURL, payload)
|
req, err := http.NewRequest("PATCH", reqURL, payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -383,13 +421,20 @@ func (am *Auth0Manager) UpdateUserAppMetadata(userID string, appMetadata AppMeta
|
|||||||
req.Header.Add("authorization", "Bearer "+jwtToken.AccessToken)
|
req.Header.Add("authorization", "Bearer "+jwtToken.AccessToken)
|
||||||
req.Header.Add("content-type", "application/json")
|
req.Header.Add("content-type", "application/json")
|
||||||
|
|
||||||
log.Debugf("updating metadata for user %s", userID)
|
log.Debugf("updating IdP metadata for user %s", userID)
|
||||||
|
|
||||||
res, err := am.httpClient.Do(req)
|
res, err := am.httpClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if am.appMetrics != nil {
|
||||||
|
am.appMetrics.IDPMetrics().CountRequestError()
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if am.appMetrics != nil {
|
||||||
|
am.appMetrics.IDPMetrics().CountUpdateUserAppMetadata()
|
||||||
|
}
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
err = res.Body.Close()
|
err = res.Body.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -404,6 +449,28 @@ func (am *Auth0Manager) UpdateUserAppMetadata(userID string, appMetadata AppMeta
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func buildCreateUserRequestPayload(email string, name string, accountID string) (string, error) {
|
||||||
|
invite := true
|
||||||
|
req := &createUserRequest{
|
||||||
|
Email: email,
|
||||||
|
Name: name,
|
||||||
|
AppMeta: AppMetadata{
|
||||||
|
WTAccountID: accountID,
|
||||||
|
WTPendingInvite: &invite,
|
||||||
|
},
|
||||||
|
Connection: "Username-Password-Authentication",
|
||||||
|
Password: GeneratePassword(8, 1, 1, 1),
|
||||||
|
VerifyEmail: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
str, err := json.Marshal(req)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(str), nil
|
||||||
|
}
|
||||||
|
|
||||||
func buildUserExportRequest() (string, error) {
|
func buildUserExportRequest() (string, error) {
|
||||||
req := &userExportJobRequest{}
|
req := &userExportJobRequest{}
|
||||||
fields := make([]map[string]string, 0)
|
fields := make([]map[string]string, 0)
|
||||||
@@ -417,6 +484,11 @@ func buildUserExportRequest() (string, error) {
|
|||||||
"export_as": "wt_account_id",
|
"export_as": "wt_account_id",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
fields = append(fields, map[string]string{
|
||||||
|
"name": "app_metadata.wt_pending_invite",
|
||||||
|
"export_as": "wt_pending_invite",
|
||||||
|
})
|
||||||
|
|
||||||
req.Format = "json"
|
req.Format = "json"
|
||||||
req.Fields = fields
|
req.Fields = fields
|
||||||
|
|
||||||
@@ -428,32 +500,46 @@ func buildUserExportRequest() (string, error) {
|
|||||||
return string(str), nil
|
return string(str), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAllAccounts gets all registered accounts with corresponding user data.
|
func (am *Auth0Manager) createPostRequest(endpoint string, payloadStr string) (*http.Request, error) {
|
||||||
// It returns a list of users indexed by accountID.
|
|
||||||
func (am *Auth0Manager) GetAllAccounts() (map[string][]*UserData, error) {
|
|
||||||
jwtToken, err := am.credentials.Authenticate()
|
jwtToken, err := am.credentials.Authenticate()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
reqURL := am.authIssuer + "/api/v2/jobs/users-exports"
|
reqURL := am.authIssuer + endpoint
|
||||||
|
|
||||||
|
payload := strings.NewReader(payloadStr)
|
||||||
|
|
||||||
|
req, err := http.NewRequest("POST", reqURL, payload)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req.Header.Add("authorization", "Bearer "+jwtToken.AccessToken)
|
||||||
|
req.Header.Add("content-type", "application/json")
|
||||||
|
|
||||||
|
return req, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAllAccounts gets all registered accounts with corresponding user data.
|
||||||
|
// It returns a list of users indexed by accountID.
|
||||||
|
func (am *Auth0Manager) GetAllAccounts() (map[string][]*UserData, error) {
|
||||||
payloadString, err := buildUserExportRequest()
|
payloadString, err := buildUserExportRequest()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
payload := strings.NewReader(payloadString)
|
|
||||||
|
|
||||||
exportJobReq, err := http.NewRequest("POST", reqURL, payload)
|
exportJobReq, err := am.createPostRequest("/api/v2/jobs/users-exports", payloadString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
exportJobReq.Header.Add("authorization", "Bearer "+jwtToken.AccessToken)
|
|
||||||
exportJobReq.Header.Add("content-type", "application/json")
|
|
||||||
|
|
||||||
jobResp, err := am.httpClient.Do(exportJobReq)
|
jobResp, err := am.httpClient.Do(exportJobReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debugf("Couldn't get job response %v", err)
|
log.Debugf("Couldn't get job response %v", err)
|
||||||
|
if am.appMetrics != nil {
|
||||||
|
am.appMetrics.IDPMetrics().CountRequestError()
|
||||||
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -464,12 +550,15 @@ func (am *Auth0Manager) GetAllAccounts() (map[string][]*UserData, error) {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
if jobResp.StatusCode != 200 {
|
if jobResp.StatusCode != 200 {
|
||||||
|
if am.appMetrics != nil {
|
||||||
|
am.appMetrics.IDPMetrics().CountRequestStatusError()
|
||||||
|
}
|
||||||
return nil, fmt.Errorf("unable to update the appMetadata, statusCode %d", jobResp.StatusCode)
|
return nil, fmt.Errorf("unable to update the appMetadata, statusCode %d", jobResp.StatusCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
var exportJobResp userExportJobResponse
|
var exportJobResp userExportJobResponse
|
||||||
|
|
||||||
body, err := ioutil.ReadAll(jobResp.Body)
|
body, err := io.ReadAll(jobResp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debugf("Coudln't read export job response; %v", err)
|
log.Debugf("Coudln't read export job response; %v", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -482,6 +571,9 @@ func (am *Auth0Manager) GetAllAccounts() (map[string][]*UserData, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if exportJobResp.ID == "" {
|
if exportJobResp.ID == "" {
|
||||||
|
if am.appMetrics != nil {
|
||||||
|
am.appMetrics.IDPMetrics().CountRequestStatusError()
|
||||||
|
}
|
||||||
return nil, fmt.Errorf("couldn't get an batch id status %d, %s, response body: %v", jobResp.StatusCode, jobResp.Status, exportJobResp)
|
return nil, fmt.Errorf("couldn't get an batch id status %d, %s, response body: %v", jobResp.StatusCode, jobResp.Status, exportJobResp)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -500,6 +592,96 @@ func (am *Auth0Manager) GetAllAccounts() (map[string][]*UserData, error) {
|
|||||||
return nil, fmt.Errorf("failed extracting user profiles from auth0")
|
return nil, fmt.Errorf("failed extracting user profiles from auth0")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetUserByEmail searches users with a given email. If no users have been found, this function returns an empty list.
|
||||||
|
// This function can return multiple users. This is due to the Auth0 internals - there could be multiple users with
|
||||||
|
// the same email but different connections that are considered as separate accounts (e.g., Google and username/password).
|
||||||
|
func (am *Auth0Manager) GetUserByEmail(email string) ([]*UserData, error) {
|
||||||
|
jwtToken, err := am.credentials.Authenticate()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
reqURL := am.authIssuer + "/api/v2/users-by-email?email=" + url.QueryEscape(email)
|
||||||
|
body, err := doGetReq(am.httpClient, reqURL, jwtToken.AccessToken)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if am.appMetrics != nil {
|
||||||
|
am.appMetrics.IDPMetrics().CountGetUserByEmail()
|
||||||
|
}
|
||||||
|
|
||||||
|
userResp := []*UserData{}
|
||||||
|
|
||||||
|
err = am.helper.Unmarshal(body, &userResp)
|
||||||
|
if err != nil {
|
||||||
|
log.Debugf("Coudln't unmarshal export job response; %v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return userResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateUser creates a new user in Auth0 Idp and sends an invite
|
||||||
|
func (am *Auth0Manager) CreateUser(email string, name string, accountID string) (*UserData, error) {
|
||||||
|
|
||||||
|
payloadString, err := buildCreateUserRequestPayload(email, name, accountID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req, err := am.createPostRequest("/api/v2/users", payloadString)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if am.appMetrics != nil {
|
||||||
|
am.appMetrics.IDPMetrics().CountCreateUser()
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := am.httpClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
log.Debugf("Couldn't get job response %v", err)
|
||||||
|
if am.appMetrics != nil {
|
||||||
|
am.appMetrics.IDPMetrics().CountRequestError()
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
err = resp.Body.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("error while closing create user response body: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
if !(resp.StatusCode == 200 || resp.StatusCode == 201) {
|
||||||
|
if am.appMetrics != nil {
|
||||||
|
am.appMetrics.IDPMetrics().CountRequestStatusError()
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("unable to create user, statusCode %d", resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
var createResp UserData
|
||||||
|
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
log.Debugf("Coudln't read export job response; %v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = am.helper.Unmarshal(body, &createResp)
|
||||||
|
if err != nil {
|
||||||
|
log.Debugf("Coudln't unmarshal export job response; %v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if createResp.ID == "" {
|
||||||
|
return nil, fmt.Errorf("couldn't create user: response %v", resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf("created user %s in account %s", createResp.ID, accountID)
|
||||||
|
|
||||||
|
return &createResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
// checkExportJobStatus checks the status of the job created at CreateExportUsersJob.
|
// checkExportJobStatus checks the status of the job created at CreateExportUsersJob.
|
||||||
// If the status is "completed", then return the downloadLink
|
// If the status is "completed", then return the downloadLink
|
||||||
func (am *Auth0Manager) checkExportJobStatus(jobID string) (bool, string, error) {
|
func (am *Auth0Manager) checkExportJobStatus(jobID string) (bool, string, error) {
|
||||||
@@ -572,6 +754,10 @@ func (am *Auth0Manager) downloadProfileExport(location string) (map[string][]*Us
|
|||||||
ID: profile.UserID,
|
ID: profile.UserID,
|
||||||
Name: profile.Name,
|
Name: profile.Name,
|
||||||
Email: profile.Email,
|
Email: profile.Email,
|
||||||
|
AppMetadata: AppMetadata{
|
||||||
|
WTAccountID: profile.AccountID,
|
||||||
|
WTPendingInvite: &profile.PendingInvite,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -601,13 +787,12 @@ func doGetReq(client ManagerHTTPClient, url, accessToken string) ([]byte, error)
|
|||||||
log.Errorf("error while closing body for url %s: %v", url, err)
|
log.Errorf("error while closing body for url %s: %v", url, err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
if res.StatusCode != 200 {
|
body, err := io.ReadAll(res.Body)
|
||||||
return nil, fmt.Errorf("unable to get %s, statusCode %d", url, res.StatusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
body, err := ioutil.ReadAll(res.Body)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if res.StatusCode != 200 {
|
||||||
|
return nil, fmt.Errorf("unable to get %s, statusCode %d", url, res.StatusCode)
|
||||||
|
}
|
||||||
return body, nil
|
return body, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,9 @@ package idp
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/netbirdio/netbird/management/server/telemetry"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"io/ioutil"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -22,13 +23,13 @@ type mockHTTPClient struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *mockHTTPClient) Do(req *http.Request) (*http.Response, error) {
|
func (c *mockHTTPClient) Do(req *http.Request) (*http.Response, error) {
|
||||||
body, err := ioutil.ReadAll(req.Body)
|
body, err := io.ReadAll(req.Body)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
c.reqBody = string(body)
|
c.reqBody = string(body)
|
||||||
}
|
}
|
||||||
return &http.Response{
|
return &http.Response{
|
||||||
StatusCode: c.code,
|
StatusCode: c.code,
|
||||||
Body: ioutil.NopCloser(strings.NewReader(c.resBody)),
|
Body: io.NopCloser(strings.NewReader(c.resBody)),
|
||||||
}, c.err
|
}, c.err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,7 +131,7 @@ func TestAuth0_RequestJWTToken(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
body, err := ioutil.ReadAll(res.Body)
|
body, err := io.ReadAll(res.Body)
|
||||||
assert.NoError(t, err, "unable to read the response body")
|
assert.NoError(t, err, "unable to read the response body")
|
||||||
|
|
||||||
jwtToken := JWTToken{}
|
jwtToken := JWTToken{}
|
||||||
@@ -178,7 +179,7 @@ func TestAuth0_ParseRequestJWTResponse(t *testing.T) {
|
|||||||
for _, testCase := range []parseRequestJWTResponseTest{parseRequestJWTResponseTestCase1, parseRequestJWTResponseTestCase2} {
|
for _, testCase := range []parseRequestJWTResponseTest{parseRequestJWTResponseTestCase1, parseRequestJWTResponseTestCase2} {
|
||||||
t.Run(testCase.name, func(t *testing.T) {
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
|
|
||||||
rawBody := ioutil.NopCloser(strings.NewReader(testCase.inputResBody))
|
rawBody := io.NopCloser(strings.NewReader(testCase.inputResBody))
|
||||||
|
|
||||||
config := Auth0ClientConfig{}
|
config := Auth0ClientConfig{}
|
||||||
|
|
||||||
@@ -320,7 +321,7 @@ func TestAuth0_UpdateUserAppMetadata(t *testing.T) {
|
|||||||
|
|
||||||
exp := 15
|
exp := 15
|
||||||
token := newTestJWT(t, exp)
|
token := newTestJWT(t, exp)
|
||||||
appMetadata := AppMetadata{WTAccountId: "ok"}
|
appMetadata := AppMetadata{WTAccountID: "ok"}
|
||||||
|
|
||||||
updateUserAppMetadataTestCase1 := updateUserAppMetadataTest{
|
updateUserAppMetadataTestCase1 := updateUserAppMetadataTest{
|
||||||
name: "Bad Authentication",
|
name: "Bad Authentication",
|
||||||
@@ -340,7 +341,7 @@ func TestAuth0_UpdateUserAppMetadata(t *testing.T) {
|
|||||||
updateUserAppMetadataTestCase2 := updateUserAppMetadataTest{
|
updateUserAppMetadataTestCase2 := updateUserAppMetadataTest{
|
||||||
name: "Bad Status Code",
|
name: "Bad Status Code",
|
||||||
inputReqBody: fmt.Sprintf("{\"access_token\":\"%s\",\"scope\":\"read:users\",\"expires_in\":%d,\"token_type\":\"Bearer\"}", token, exp),
|
inputReqBody: fmt.Sprintf("{\"access_token\":\"%s\",\"scope\":\"read:users\",\"expires_in\":%d,\"token_type\":\"Bearer\"}", token, exp),
|
||||||
expectedReqBody: fmt.Sprintf("{\"app_metadata\": {\"wt_account_id\":\"%s\"}}", appMetadata.WTAccountId),
|
expectedReqBody: fmt.Sprintf("{\"app_metadata\":{\"wt_account_id\":\"%s\",\"wt_pending_invite\":null}}", appMetadata.WTAccountID),
|
||||||
appMetadata: appMetadata,
|
appMetadata: appMetadata,
|
||||||
statusCode: 400,
|
statusCode: 400,
|
||||||
helper: JsonParser{},
|
helper: JsonParser{},
|
||||||
@@ -363,7 +364,7 @@ func TestAuth0_UpdateUserAppMetadata(t *testing.T) {
|
|||||||
updateUserAppMetadataTestCase4 := updateUserAppMetadataTest{
|
updateUserAppMetadataTestCase4 := updateUserAppMetadataTest{
|
||||||
name: "Good request",
|
name: "Good request",
|
||||||
inputReqBody: fmt.Sprintf("{\"access_token\":\"%s\",\"scope\":\"read:users\",\"expires_in\":%d,\"token_type\":\"Bearer\"}", token, exp),
|
inputReqBody: fmt.Sprintf("{\"access_token\":\"%s\",\"scope\":\"read:users\",\"expires_in\":%d,\"token_type\":\"Bearer\"}", token, exp),
|
||||||
expectedReqBody: fmt.Sprintf("{\"app_metadata\": {\"wt_account_id\":\"%s\"}}", appMetadata.WTAccountId),
|
expectedReqBody: fmt.Sprintf("{\"app_metadata\":{\"wt_account_id\":\"%s\",\"wt_pending_invite\":null}}", appMetadata.WTAccountID),
|
||||||
appMetadata: appMetadata,
|
appMetadata: appMetadata,
|
||||||
statusCode: 200,
|
statusCode: 200,
|
||||||
helper: JsonParser{},
|
helper: JsonParser{},
|
||||||
@@ -371,7 +372,23 @@ func TestAuth0_UpdateUserAppMetadata(t *testing.T) {
|
|||||||
assertErrFuncMessage: "shouldn't return error",
|
assertErrFuncMessage: "shouldn't return error",
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, testCase := range []updateUserAppMetadataTest{updateUserAppMetadataTestCase1, updateUserAppMetadataTestCase2, updateUserAppMetadataTestCase3, updateUserAppMetadataTestCase4} {
|
invite := true
|
||||||
|
updateUserAppMetadataTestCase5 := updateUserAppMetadataTest{
|
||||||
|
name: "Update Pending Invite",
|
||||||
|
inputReqBody: fmt.Sprintf("{\"access_token\":\"%s\",\"scope\":\"read:users\",\"expires_in\":%d,\"token_type\":\"Bearer\"}", token, exp),
|
||||||
|
expectedReqBody: fmt.Sprintf("{\"app_metadata\":{\"wt_account_id\":\"%s\",\"wt_pending_invite\":true}}", appMetadata.WTAccountID),
|
||||||
|
appMetadata: AppMetadata{
|
||||||
|
WTAccountID: "ok",
|
||||||
|
WTPendingInvite: &invite,
|
||||||
|
},
|
||||||
|
statusCode: 200,
|
||||||
|
helper: JsonParser{},
|
||||||
|
assertErrFunc: assert.NoError,
|
||||||
|
assertErrFuncMessage: "shouldn't return error",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, testCase := range []updateUserAppMetadataTest{updateUserAppMetadataTestCase1, updateUserAppMetadataTestCase2,
|
||||||
|
updateUserAppMetadataTestCase3, updateUserAppMetadataTestCase4, updateUserAppMetadataTestCase5} {
|
||||||
t.Run(testCase.name, func(t *testing.T) {
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
jwtReqClient := mockHTTPClient{
|
jwtReqClient := mockHTTPClient{
|
||||||
resBody: testCase.inputReqBody,
|
resBody: testCase.inputReqBody,
|
||||||
@@ -459,7 +476,7 @@ func TestNewAuth0Manager(t *testing.T) {
|
|||||||
|
|
||||||
for _, testCase := range []test{testCase1, testCase2, testCase3, testCase4} {
|
for _, testCase := range []test{testCase1, testCase2, testCase3, testCase4} {
|
||||||
t.Run(testCase.name, func(t *testing.T) {
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
_, err := NewAuth0Manager(testCase.inputConfig)
|
_, err := NewAuth0Manager(testCase.inputConfig, &telemetry.MockAppMetrics{})
|
||||||
testCase.assertErrFunc(t, err, testCase.assertErrFuncMessage)
|
testCase.assertErrFunc(t, err, testCase.assertErrFuncMessage)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package idp
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/netbirdio/netbird/management/server/telemetry"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -13,6 +14,8 @@ type Manager interface {
|
|||||||
GetUserDataByID(userId string, appMetadata AppMetadata) (*UserData, error)
|
GetUserDataByID(userId string, appMetadata AppMetadata) (*UserData, error)
|
||||||
GetAccount(accountId string) ([]*UserData, error)
|
GetAccount(accountId string) ([]*UserData, error)
|
||||||
GetAllAccounts() (map[string][]*UserData, error)
|
GetAllAccounts() (map[string][]*UserData, error)
|
||||||
|
CreateUser(email string, name string, accountID string) (*UserData, error)
|
||||||
|
GetUserByEmail(email string) ([]*UserData, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Config an idp configuration struct to be loaded from management server's config file
|
// Config an idp configuration struct to be loaded from management server's config file
|
||||||
@@ -38,16 +41,18 @@ type ManagerHelper interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type UserData struct {
|
type UserData struct {
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
ID string `json:"user_id"`
|
ID string `json:"user_id"`
|
||||||
|
AppMetadata AppMetadata `json:"app_metadata"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// AppMetadata user app metadata to associate with a profile
|
// AppMetadata user app metadata to associate with a profile
|
||||||
type AppMetadata struct {
|
type AppMetadata struct {
|
||||||
// Wiretrustee account id to update in the IDP
|
// WTAccountID is a NetBird (previously Wiretrustee) account id to update in the IDP
|
||||||
// maps to wt_account_id when json.marshal
|
// maps to wt_account_id when json.marshal
|
||||||
WTAccountId string `json:"wt_account_id"`
|
WTAccountID string `json:"wt_account_id,omitempty"`
|
||||||
|
WTPendingInvite *bool `json:"wt_pending_invite"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// JWTToken a JWT object that holds information of a token
|
// JWTToken a JWT object that holds information of a token
|
||||||
@@ -60,12 +65,12 @@ type JWTToken struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewManager returns a new idp manager based on the configuration that it receives
|
// NewManager returns a new idp manager based on the configuration that it receives
|
||||||
func NewManager(config Config) (Manager, error) {
|
func NewManager(config Config, appMetrics telemetry.AppMetrics) (Manager, error) {
|
||||||
switch strings.ToLower(config.ManagerType) {
|
switch strings.ToLower(config.ManagerType) {
|
||||||
case "none", "":
|
case "none", "":
|
||||||
return nil, nil
|
return nil, nil
|
||||||
case "auth0":
|
case "auth0":
|
||||||
return NewAuth0Manager(config.Auth0ClientCredentials)
|
return NewAuth0Manager(config.Auth0ClientCredentials, appMetrics)
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("invalid manager type: %s", config.ManagerType)
|
return nil, fmt.Errorf("invalid manager type: %s", config.ManagerType)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,18 @@
|
|||||||
package idp
|
package idp
|
||||||
|
|
||||||
import "encoding/json"
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"math/rand"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
lowerCharSet = "abcdedfghijklmnopqrst"
|
||||||
|
upperCharSet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||||
|
specialCharSet = "!@#$%&*"
|
||||||
|
numberSet = "0123456789"
|
||||||
|
allCharSet = lowerCharSet + upperCharSet + specialCharSet + numberSet
|
||||||
|
)
|
||||||
|
|
||||||
type JsonParser struct{}
|
type JsonParser struct{}
|
||||||
|
|
||||||
@@ -11,3 +23,37 @@ func (JsonParser) Marshal(v interface{}) ([]byte, error) {
|
|||||||
func (JsonParser) Unmarshal(data []byte, v interface{}) error {
|
func (JsonParser) Unmarshal(data []byte, v interface{}) error {
|
||||||
return json.Unmarshal(data, v)
|
return json.Unmarshal(data, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GeneratePassword generates user password
|
||||||
|
func GeneratePassword(passwordLength, minSpecialChar, minNum, minUpperCase int) string {
|
||||||
|
var password strings.Builder
|
||||||
|
|
||||||
|
//Set special character
|
||||||
|
for i := 0; i < minSpecialChar; i++ {
|
||||||
|
random := rand.Intn(len(specialCharSet))
|
||||||
|
password.WriteString(string(specialCharSet[random]))
|
||||||
|
}
|
||||||
|
|
||||||
|
//Set numeric
|
||||||
|
for i := 0; i < minNum; i++ {
|
||||||
|
random := rand.Intn(len(numberSet))
|
||||||
|
password.WriteString(string(numberSet[random]))
|
||||||
|
}
|
||||||
|
|
||||||
|
//Set uppercase
|
||||||
|
for i := 0; i < minUpperCase; i++ {
|
||||||
|
random := rand.Intn(len(upperCharSet))
|
||||||
|
password.WriteString(string(upperCharSet[random]))
|
||||||
|
}
|
||||||
|
|
||||||
|
remainingLength := passwordLength - minSpecialChar - minNum - minUpperCase
|
||||||
|
for i := 0; i < remainingLength; i++ {
|
||||||
|
random := rand.Intn(len(allCharSet))
|
||||||
|
password.WriteString(string(allCharSet[random]))
|
||||||
|
}
|
||||||
|
inRune := []rune(password.String())
|
||||||
|
rand.Shuffle(len(inRune), func(i, j int) {
|
||||||
|
inRune[i], inRune[j] = inRune[j], inRune[i]
|
||||||
|
})
|
||||||
|
return string(inRune)
|
||||||
|
}
|
||||||
|
|||||||
@@ -398,17 +398,17 @@ func startManagement(t *testing.T, port int, config *Config) (*grpc.Server, erro
|
|||||||
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 := NewStore(config.Datadir)
|
store, err := NewFileStore(config.Datadir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
peersUpdateManager := NewPeersUpdateManager()
|
peersUpdateManager := NewPeersUpdateManager()
|
||||||
accountManager, err := BuildManager(store, peersUpdateManager, nil)
|
accountManager, err := BuildManager(store, peersUpdateManager, nil, "", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
turnManager := NewTimeBasedAuthSecretsManager(peersUpdateManager, config.TURNConfig)
|
turnManager := NewTimeBasedAuthSecretsManager(peersUpdateManager, config.TURNConfig)
|
||||||
mgmtServer, err := NewServer(config, accountManager, peersUpdateManager, turnManager)
|
mgmtServer, err := NewServer(config, accountManager, peersUpdateManager, turnManager, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package server_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"io/ioutil"
|
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
@@ -45,7 +44,7 @@ var _ = Describe("Management service", func() {
|
|||||||
level, _ := log.ParseLevel("Debug")
|
level, _ := log.ParseLevel("Debug")
|
||||||
log.SetLevel(level)
|
log.SetLevel(level)
|
||||||
var err error
|
var err error
|
||||||
dataDir, err = ioutil.TempDir("", "wiretrustee_mgmt_test_tmp_*")
|
dataDir, err = os.MkdirTemp("", "wiretrustee_mgmt_test_tmp_*")
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
err = util.CopyFileContents("testdata/store.json", filepath.Join(dataDir, "store.json"))
|
err = util.CopyFileContents("testdata/store.json", filepath.Join(dataDir, "store.json"))
|
||||||
@@ -489,17 +488,17 @@ func startServer(config *server.Config) (*grpc.Server, net.Listener) {
|
|||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
s := grpc.NewServer()
|
s := grpc.NewServer()
|
||||||
|
|
||||||
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 {
|
||||||
log.Fatalf("failed creating a manager: %v", err)
|
log.Fatalf("failed creating a manager: %v", 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)
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
mgmtProto.RegisterManagementServiceServer(s, mgmtServer)
|
mgmtProto.RegisterManagementServiceServer(s, mgmtServer)
|
||||||
go func() {
|
go func() {
|
||||||
|
|||||||
337
management/server/metrics/selfhosted.go
Normal file
337
management/server/metrics/selfhosted.go
Normal file
@@ -0,0 +1,337 @@
|
|||||||
|
// Package metrics gather anonymous information about the usage of NetBird management
|
||||||
|
package metrics
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"github.com/hashicorp/go-version"
|
||||||
|
"github.com/netbirdio/netbird/client/system"
|
||||||
|
"github.com/netbirdio/netbird/management/server"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"regexp"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// PayloadEvent identifies an event type
|
||||||
|
PayloadEvent = "self-hosted stats"
|
||||||
|
// payloadEndpoint metrics defaultEndpoint to send anonymous data
|
||||||
|
payloadEndpoint = "https://metrics.netbird.io"
|
||||||
|
// defaultPushInterval default interval to push metrics
|
||||||
|
defaultPushInterval = 24 * time.Hour
|
||||||
|
// requestTimeout http request timeout
|
||||||
|
requestTimeout = 30 * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
|
type getTokenResponse struct {
|
||||||
|
PublicAPIToken string `json:"public_api_token"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type pushPayload struct {
|
||||||
|
APIKey string `json:"api_key"`
|
||||||
|
DistinctID string `json:"distinct_id"`
|
||||||
|
Event string `json:"event"`
|
||||||
|
Properties properties `json:"properties"`
|
||||||
|
Timestamp time.Time `json:"timestamp"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// properties metrics to push
|
||||||
|
type properties map[string]interface{}
|
||||||
|
|
||||||
|
// DataSource metric data source
|
||||||
|
type DataSource interface {
|
||||||
|
GetAllAccounts() []*server.Account
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConnManager peer connection manager that holds state for current active connections
|
||||||
|
type ConnManager interface {
|
||||||
|
GetAllConnectedPeers() map[string]struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Worker metrics collector and pusher
|
||||||
|
type Worker struct {
|
||||||
|
ctx context.Context
|
||||||
|
id string
|
||||||
|
dataSource DataSource
|
||||||
|
connManager ConnManager
|
||||||
|
startupTime time.Time
|
||||||
|
lastRun time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWorker returns a metrics worker
|
||||||
|
func NewWorker(ctx context.Context, id string, dataSource DataSource, connManager ConnManager) *Worker {
|
||||||
|
currentTime := time.Now()
|
||||||
|
return &Worker{
|
||||||
|
ctx: ctx,
|
||||||
|
id: id,
|
||||||
|
dataSource: dataSource,
|
||||||
|
connManager: connManager,
|
||||||
|
startupTime: currentTime,
|
||||||
|
lastRun: currentTime,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run runs the metrics worker
|
||||||
|
func (w *Worker) Run() {
|
||||||
|
pushTicker := time.NewTicker(defaultPushInterval)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-w.ctx.Done():
|
||||||
|
return
|
||||||
|
case <-pushTicker.C:
|
||||||
|
err := w.sendMetrics()
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
w.lastRun = time.Now()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Worker) sendMetrics() error {
|
||||||
|
ctx, cancel := context.WithTimeout(w.ctx, requestTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
apiKey, err := getAPIKey(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
payload := w.generatePayload(apiKey)
|
||||||
|
|
||||||
|
payloadString, err := buildMetricsPayload(payload)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
httpClient := http.Client{}
|
||||||
|
|
||||||
|
exportJobReq, err := createPostRequest(ctx, payloadEndpoint+"/capture/", payloadString)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to create metrics post request %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
jobResp, err := httpClient.Do(exportJobReq)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to push metrics %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
err = jobResp.Body.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("error while closing update metrics response body: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if jobResp.StatusCode != 200 {
|
||||||
|
return fmt.Errorf("unable to push anonymous metrics, got statusCode %d", jobResp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("sent anonymous metrics, next push will happen in %s. "+
|
||||||
|
"You can disable these metrics by running with flag --disable-anonymous-metrics,"+
|
||||||
|
" see more information at https://netbird.io/docs/FAQ/metrics-collection", defaultPushInterval)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Worker) generatePayload(apiKey string) pushPayload {
|
||||||
|
properties := w.generateProperties()
|
||||||
|
|
||||||
|
return pushPayload{
|
||||||
|
APIKey: apiKey,
|
||||||
|
DistinctID: w.id,
|
||||||
|
Event: PayloadEvent,
|
||||||
|
Properties: properties,
|
||||||
|
Timestamp: time.Now(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Worker) generateProperties() properties {
|
||||||
|
var (
|
||||||
|
uptime float64
|
||||||
|
accounts int
|
||||||
|
users int
|
||||||
|
peers int
|
||||||
|
setupKeysUsage int
|
||||||
|
activePeersLastDay int
|
||||||
|
osPeers map[string]int
|
||||||
|
userPeers int
|
||||||
|
rules int
|
||||||
|
groups int
|
||||||
|
routes int
|
||||||
|
nameservers int
|
||||||
|
uiClient int
|
||||||
|
version string
|
||||||
|
peerActiveVersions []string
|
||||||
|
osUIClients map[string]int
|
||||||
|
)
|
||||||
|
start := time.Now()
|
||||||
|
metricsProperties := make(properties)
|
||||||
|
osPeers = make(map[string]int)
|
||||||
|
osUIClients = make(map[string]int)
|
||||||
|
uptime = time.Since(w.startupTime).Seconds()
|
||||||
|
connections := w.connManager.GetAllConnectedPeers()
|
||||||
|
version = system.NetbirdVersion()
|
||||||
|
|
||||||
|
for _, account := range w.dataSource.GetAllAccounts() {
|
||||||
|
accounts++
|
||||||
|
users = users + len(account.Users)
|
||||||
|
rules = rules + len(account.Rules)
|
||||||
|
groups = groups + len(account.Groups)
|
||||||
|
routes = routes + len(account.Routes)
|
||||||
|
nameservers = nameservers + len(account.NameServerGroups)
|
||||||
|
|
||||||
|
for _, key := range account.SetupKeys {
|
||||||
|
setupKeysUsage = setupKeysUsage + key.UsedTimes
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, peer := range account.Peers {
|
||||||
|
peers++
|
||||||
|
if peer.SetupKey == "" {
|
||||||
|
userPeers++
|
||||||
|
}
|
||||||
|
|
||||||
|
osKey := strings.ToLower(fmt.Sprintf("peer_os_%s", peer.Meta.GoOS))
|
||||||
|
osCount := osPeers[osKey]
|
||||||
|
osPeers[osKey] = osCount + 1
|
||||||
|
|
||||||
|
if peer.Meta.UIVersion != "" {
|
||||||
|
uiClient++
|
||||||
|
uiOSKey := strings.ToLower(fmt.Sprintf("ui_client_os_%s", peer.Meta.GoOS))
|
||||||
|
osUICount := osUIClients[uiOSKey]
|
||||||
|
osUIClients[uiOSKey] = osUICount + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
_, connected := connections[peer.Key]
|
||||||
|
if connected || peer.Status.LastSeen.After(w.lastRun) {
|
||||||
|
activePeersLastDay++
|
||||||
|
osActiveKey := osKey + "_active"
|
||||||
|
osActiveCount := osPeers[osActiveKey]
|
||||||
|
osPeers[osActiveKey] = osActiveCount + 1
|
||||||
|
peerActiveVersions = append(peerActiveVersions, peer.Meta.WtVersion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
minActivePeerVersion, maxActivePeerVersion := getMinMaxVersion(peerActiveVersions)
|
||||||
|
metricsProperties["uptime"] = uptime
|
||||||
|
metricsProperties["accounts"] = accounts
|
||||||
|
metricsProperties["users"] = users
|
||||||
|
metricsProperties["peers"] = peers
|
||||||
|
metricsProperties["setup_keys_usage"] = setupKeysUsage
|
||||||
|
metricsProperties["active_peers_last_day"] = activePeersLastDay
|
||||||
|
metricsProperties["user_peers"] = userPeers
|
||||||
|
metricsProperties["rules"] = rules
|
||||||
|
metricsProperties["groups"] = groups
|
||||||
|
metricsProperties["routes"] = routes
|
||||||
|
metricsProperties["nameservers"] = nameservers
|
||||||
|
metricsProperties["version"] = version
|
||||||
|
metricsProperties["min_active_peer_version"] = minActivePeerVersion
|
||||||
|
metricsProperties["max_active_peer_version"] = maxActivePeerVersion
|
||||||
|
metricsProperties["ui_clients"] = uiClient
|
||||||
|
for os, count := range osPeers {
|
||||||
|
metricsProperties[os] = count
|
||||||
|
}
|
||||||
|
|
||||||
|
for os, count := range osUIClients {
|
||||||
|
metricsProperties[os] = count
|
||||||
|
}
|
||||||
|
|
||||||
|
metricsProperties["metric_generation_time"] = time.Since(start).Milliseconds()
|
||||||
|
|
||||||
|
return metricsProperties
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAPIKey(ctx context.Context) (string, error) {
|
||||||
|
|
||||||
|
httpClient := http.Client{}
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, payloadEndpoint+"/GetToken", nil)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("unable to create request for metrics public api token %v", err)
|
||||||
|
}
|
||||||
|
response, err := httpClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("unable to request metrics public api token %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
err = response.Body.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("error while closing metrics token response body: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if response.StatusCode != 200 {
|
||||||
|
return "", fmt.Errorf("unable to retrieve metrics token, statusCode %d", response.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := io.ReadAll(response.Body)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("coudln't get metrics token response; %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var tokenResponse getTokenResponse
|
||||||
|
|
||||||
|
err = json.Unmarshal(body, &tokenResponse)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("coudln't parse metrics public api token; %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return tokenResponse.PublicAPIToken, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildMetricsPayload(payload pushPayload) (string, error) {
|
||||||
|
str, err := json.Marshal(payload)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("unable to marshal metrics payload, got err: %v", err)
|
||||||
|
}
|
||||||
|
return string(str), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createPostRequest(ctx context.Context, endpoint string, payloadStr string) (*http.Request, error) {
|
||||||
|
reqURL := endpoint
|
||||||
|
|
||||||
|
payload := strings.NewReader(payloadStr)
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(ctx, "POST", reqURL, payload)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req.Header.Add("content-type", "application/json")
|
||||||
|
|
||||||
|
return req, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getMinMaxVersion(inputList []string) (string, string) {
|
||||||
|
reg, err := regexp.Compile(version.SemverRegexpRaw)
|
||||||
|
if err != nil {
|
||||||
|
return "", ""
|
||||||
|
}
|
||||||
|
|
||||||
|
versions := make([]*version.Version, 0)
|
||||||
|
|
||||||
|
for _, raw := range inputList {
|
||||||
|
if raw != "" && reg.MatchString(raw) {
|
||||||
|
v, err := version.NewVersion(raw)
|
||||||
|
if err == nil {
|
||||||
|
versions = append(versions, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch len(versions) {
|
||||||
|
case 0:
|
||||||
|
return "", ""
|
||||||
|
case 1:
|
||||||
|
v := versions[0].String()
|
||||||
|
return v, v
|
||||||
|
default:
|
||||||
|
sort.Sort(version.Collection(versions))
|
||||||
|
return versions[0].String(), versions[len(versions)-1].String()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
## Migration from Store v2 to Store v2
|
|
||||||
|
|
||||||
Previously Account.Id was an Auth0 user id.
|
|
||||||
Conversion moves user id to Account.CreatedBy and generates a new Account.Id using xid.
|
|
||||||
It also adds a User with id = old Account.Id with a role Admin.
|
|
||||||
|
|
||||||
To start a conversion simply run the command below providing your current Wiretrustee Management datadir (where store.json file is located)
|
|
||||||
and a new data directory location (where a converted store.js will be stored):
|
|
||||||
```shell
|
|
||||||
./migration --oldDir /var/wiretrustee/datadir --newDir /var/wiretrustee/newdatadir/
|
|
||||||
```
|
|
||||||
|
|
||||||
Afterwards you can run the Management service providing ```/var/wiretrustee/newdatadir/ ``` as a datadir.
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"github.com/netbirdio/netbird/management/server"
|
|
||||||
"github.com/rs/xid"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
|
|
||||||
oldDir := flag.String("oldDir", "old store directory", "/var/wiretrustee/datadir")
|
|
||||||
newDir := flag.String("newDir", "new store directory", "/var/wiretrustee/newdatadir")
|
|
||||||
|
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
oldStore, err := server.NewStore(*oldDir)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
newStore, err := server.NewStore(*newDir)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = Convert(oldStore, newStore)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println("successfully converted")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert converts old store ato a new store
|
|
||||||
// Previously Account.Id was an Auth0 user id
|
|
||||||
// Conversion moved user id to Account.CreatedBy and generated a new Account.Id using xid
|
|
||||||
// It also adds a User with id = old Account.Id with a role Admin
|
|
||||||
func Convert(oldStore *server.FileStore, newStore *server.FileStore) error {
|
|
||||||
for _, account := range oldStore.Accounts {
|
|
||||||
accountCopy := account.Copy()
|
|
||||||
accountCopy.Id = xid.New().String()
|
|
||||||
accountCopy.CreatedBy = account.Id
|
|
||||||
accountCopy.Users[account.Id] = &server.User{
|
|
||||||
Id: account.Id,
|
|
||||||
Role: server.UserRoleAdmin,
|
|
||||||
}
|
|
||||||
|
|
||||||
err := newStore.SaveAccount(accountCopy)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/netbirdio/netbird/management/server"
|
|
||||||
"github.com/netbirdio/netbird/util"
|
|
||||||
"path/filepath"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestConvertAccounts(t *testing.T) {
|
|
||||||
|
|
||||||
storeDir := t.TempDir()
|
|
||||||
|
|
||||||
err := util.CopyFileContents("../testdata/storev1.json", filepath.Join(storeDir, "store.json"))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
store, err := server.NewStore(storeDir)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
convertedStore, err := server.NewStore(filepath.Join(storeDir, "converted"))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = Convert(store, convertedStore)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(store.Accounts) != len(convertedStore.Accounts) {
|
|
||||||
t.Errorf("expecting the same number of accounts after conversion")
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, account := range store.Accounts {
|
|
||||||
convertedAccount, err := convertedStore.GetUserAccount(account.Id)
|
|
||||||
if err != nil || convertedAccount == nil {
|
|
||||||
t.Errorf("expecting Account %s to be converted", account.Id)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if convertedAccount.CreatedBy != account.Id {
|
|
||||||
t.Errorf("expecting converted Account.CreatedBy field to be equal to the old Account.Id")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if convertedAccount.Id == account.Id {
|
|
||||||
t.Errorf("expecting converted Account.Id to be different from Account.Id")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if len(convertedAccount.Users) != 1 {
|
|
||||||
t.Errorf("expecting converted Account.Users to be of size 1")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
user := convertedAccount.Users[account.Id]
|
|
||||||
if user == nil {
|
|
||||||
t.Errorf("expecting to find a user in converted Account.Users")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if user.Role != server.UserRoleAdmin {
|
|
||||||
t.Errorf("expecting to find a user in converted Account.Users with a role Admin")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for peerId := range account.Peers {
|
|
||||||
convertedPeer := convertedAccount.Peers[peerId]
|
|
||||||
if convertedPeer == nil {
|
|
||||||
t.Errorf("expecting Account Peer of StoreV1 to be found in StoreV2")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package mock_server
|
package mock_server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
nbdns "github.com/netbirdio/netbird/dns"
|
||||||
"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"
|
||||||
"github.com/netbirdio/netbird/route"
|
"github.com/netbirdio/netbird/route"
|
||||||
@@ -10,57 +11,73 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type MockAccountManager struct {
|
type MockAccountManager struct {
|
||||||
GetOrCreateAccountByUserFunc func(userId, domain string) (*server.Account, error)
|
GetOrCreateAccountByUserFunc func(userId, domain string) (*server.Account, error)
|
||||||
GetAccountByUserFunc func(userId string) (*server.Account, error)
|
GetAccountByUserFunc func(userId string) (*server.Account, error)
|
||||||
AddSetupKeyFunc func(accountId string, keyName string, keyType server.SetupKeyType, expiresIn time.Duration) (*server.SetupKey, error)
|
CreateSetupKeyFunc func(accountId string, keyName string, keyType server.SetupKeyType, expiresIn time.Duration, autoGroups []string) (*server.SetupKey, error)
|
||||||
RevokeSetupKeyFunc func(accountId string, keyId string) (*server.SetupKey, error)
|
GetSetupKeyFunc func(accountID, userID, keyID string) (*server.SetupKey, error)
|
||||||
RenameSetupKeyFunc func(accountId string, keyId string, newName string) (*server.SetupKey, error)
|
GetAccountByUserOrAccountIdFunc func(userId, accountId, domain string) (*server.Account, error)
|
||||||
GetAccountByIdFunc func(accountId string) (*server.Account, error)
|
IsUserAdminFunc func(claims jwtclaims.AuthorizationClaims) (bool, error)
|
||||||
GetAccountByUserOrAccountIdFunc func(userId, accountId, domain string) (*server.Account, error)
|
AccountExistsFunc func(accountId string) (*bool, error)
|
||||||
GetAccountWithAuthorizationClaimsFunc func(claims jwtclaims.AuthorizationClaims) (*server.Account, error)
|
GetPeerFunc func(peerKey string) (*server.Peer, error)
|
||||||
IsUserAdminFunc func(claims jwtclaims.AuthorizationClaims) (bool, error)
|
GetPeersFunc func(accountID, userID string) ([]*server.Peer, error)
|
||||||
AccountExistsFunc func(accountId string) (*bool, error)
|
MarkPeerConnectedFunc func(peerKey string, connected bool) error
|
||||||
GetPeerFunc func(peerKey string) (*server.Peer, error)
|
DeletePeerFunc func(accountId string, peerKey string) (*server.Peer, error)
|
||||||
MarkPeerConnectedFunc func(peerKey string, connected bool) error
|
GetPeerByIPFunc func(accountId string, peerIP string) (*server.Peer, error)
|
||||||
RenamePeerFunc func(accountId string, peerKey string, newName string) (*server.Peer, error)
|
GetNetworkMapFunc func(peerKey string) (*server.NetworkMap, error)
|
||||||
DeletePeerFunc func(accountId string, peerKey string) (*server.Peer, error)
|
GetPeerNetworkFunc func(peerKey string) (*server.Network, error)
|
||||||
GetPeerByIPFunc func(accountId string, peerIP string) (*server.Peer, error)
|
AddPeerFunc func(setupKey string, userId string, peer *server.Peer) (*server.Peer, error)
|
||||||
GetNetworkMapFunc func(peerKey string) (*server.NetworkMap, error)
|
GetGroupFunc func(accountID, groupID string) (*server.Group, error)
|
||||||
GetPeerNetworkFunc func(peerKey string) (*server.Network, error)
|
SaveGroupFunc func(accountID string, group *server.Group) error
|
||||||
AddPeerFunc func(setupKey string, userId string, peer *server.Peer) (*server.Peer, error)
|
UpdateGroupFunc func(accountID string, groupID string, operations []server.GroupUpdateOperation) (*server.Group, error)
|
||||||
GetGroupFunc func(accountID, groupID string) (*server.Group, error)
|
DeleteGroupFunc func(accountID, groupID string) error
|
||||||
SaveGroupFunc func(accountID string, group *server.Group) error
|
ListGroupsFunc func(accountID string) ([]*server.Group, error)
|
||||||
UpdateGroupFunc func(accountID string, groupID string, operations []server.GroupUpdateOperation) (*server.Group, error)
|
GroupAddPeerFunc func(accountID, groupID, peerKey string) error
|
||||||
DeleteGroupFunc func(accountID, groupID string) error
|
GroupDeletePeerFunc func(accountID, groupID, peerKey string) error
|
||||||
ListGroupsFunc func(accountID string) ([]*server.Group, error)
|
GroupListPeersFunc func(accountID, groupID string) ([]*server.Peer, error)
|
||||||
GroupAddPeerFunc func(accountID, groupID, peerKey string) error
|
GetRuleFunc func(accountID, ruleID, userID string) (*server.Rule, error)
|
||||||
GroupDeletePeerFunc func(accountID, groupID, peerKey string) error
|
SaveRuleFunc func(accountID string, rule *server.Rule) error
|
||||||
GroupListPeersFunc func(accountID, groupID string) ([]*server.Peer, error)
|
UpdateRuleFunc func(accountID string, ruleID string, operations []server.RuleUpdateOperation) (*server.Rule, error)
|
||||||
GetRuleFunc func(accountID, ruleID string) (*server.Rule, error)
|
DeleteRuleFunc func(accountID, ruleID string) error
|
||||||
SaveRuleFunc func(accountID string, rule *server.Rule) error
|
ListRulesFunc func(accountID, userID string) ([]*server.Rule, error)
|
||||||
UpdateRuleFunc func(accountID string, ruleID string, operations []server.RuleUpdateOperation) (*server.Rule, error)
|
GetUsersFromAccountFunc func(accountID, userID string) ([]*server.UserInfo, error)
|
||||||
DeleteRuleFunc func(accountID, ruleID string) error
|
UpdatePeerMetaFunc func(peerKey string, meta server.PeerSystemMeta) error
|
||||||
ListRulesFunc func(accountID string) ([]*server.Rule, error)
|
UpdatePeerSSHKeyFunc func(peerKey string, sshKey string) error
|
||||||
GetUsersFromAccountFunc func(accountID string) ([]*server.UserInfo, error)
|
UpdatePeerFunc func(accountID string, peer *server.Peer) (*server.Peer, error)
|
||||||
UpdatePeerMetaFunc func(peerKey string, meta server.PeerSystemMeta) error
|
CreateRouteFunc func(accountID string, prefix, peer, description, netID string, masquerade bool, metric int, enabled bool) (*route.Route, error)
|
||||||
UpdatePeerSSHKeyFunc func(peerKey string, sshKey string) error
|
GetRouteFunc func(accountID, routeID, userID string) (*route.Route, error)
|
||||||
UpdatePeerFunc func(accountID string, peer *server.Peer) (*server.Peer, error)
|
SaveRouteFunc func(accountID string, route *route.Route) error
|
||||||
CreateRouteFunc func(accountID string, prefix, peer, description, netID string, masquerade bool, metric int, enabled bool) (*route.Route, error)
|
UpdateRouteFunc func(accountID string, routeID string, operations []server.RouteUpdateOperation) (*route.Route, error)
|
||||||
GetRouteFunc func(accountID, routeID string) (*route.Route, error)
|
DeleteRouteFunc func(accountID, routeID string) error
|
||||||
SaveRouteFunc func(accountID string, route *route.Route) error
|
ListRoutesFunc func(accountID, userID string) ([]*route.Route, error)
|
||||||
UpdateRouteFunc func(accountID string, routeID string, operations []server.RouteUpdateOperation) (*route.Route, error)
|
SaveSetupKeyFunc func(accountID string, key *server.SetupKey) (*server.SetupKey, error)
|
||||||
DeleteRouteFunc func(accountID, routeID string) error
|
ListSetupKeysFunc func(accountID, userID string) ([]*server.SetupKey, error)
|
||||||
ListRoutesFunc func(accountID string) ([]*route.Route, error)
|
SaveUserFunc func(accountID string, user *server.User) (*server.UserInfo, error)
|
||||||
|
GetNameServerGroupFunc func(accountID, nsGroupID string) (*nbdns.NameServerGroup, error)
|
||||||
|
CreateNameServerGroupFunc func(accountID string, name, description string, nameServerList []nbdns.NameServer, groups []string, primary bool, domains []string, enabled bool) (*nbdns.NameServerGroup, error)
|
||||||
|
SaveNameServerGroupFunc func(accountID string, nsGroupToSave *nbdns.NameServerGroup) error
|
||||||
|
UpdateNameServerGroupFunc func(accountID, nsGroupID string, operations []server.NameServerGroupUpdateOperation) (*nbdns.NameServerGroup, error)
|
||||||
|
DeleteNameServerGroupFunc func(accountID, nsGroupID string) error
|
||||||
|
ListNameServerGroupsFunc func(accountID string) ([]*nbdns.NameServerGroup, error)
|
||||||
|
CreateUserFunc func(accountID string, key *server.UserInfo) (*server.UserInfo, error)
|
||||||
|
GetAccountFromTokenFunc func(claims jwtclaims.AuthorizationClaims) (*server.Account, *server.User, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUsersFromAccount mock implementation of GetUsersFromAccount from server.AccountManager interface
|
// GetUsersFromAccount mock implementation of GetUsersFromAccount from server.AccountManager interface
|
||||||
func (am *MockAccountManager) GetUsersFromAccount(accountID string) ([]*server.UserInfo, error) {
|
func (am *MockAccountManager) GetUsersFromAccount(accountID string, userID string) ([]*server.UserInfo, error) {
|
||||||
if am.GetUsersFromAccountFunc != nil {
|
if am.GetUsersFromAccountFunc != nil {
|
||||||
return am.GetUsersFromAccountFunc(accountID)
|
return am.GetUsersFromAccountFunc(accountID, userID)
|
||||||
}
|
}
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method GetUsersFromAccount is not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method GetUsersFromAccount is not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeletePeer mock implementation of DeletePeer from server.AccountManager interface
|
||||||
|
func (am *MockAccountManager) DeletePeer(accountId string, peerKey string) (*server.Peer, error) {
|
||||||
|
if am.DeletePeerFunc != nil {
|
||||||
|
return am.DeletePeerFunc(accountId, peerKey)
|
||||||
|
}
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method DeletePeer is not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
// GetOrCreateAccountByUser mock implementation of GetOrCreateAccountByUser from server.AccountManager interface
|
// GetOrCreateAccountByUser mock implementation of GetOrCreateAccountByUser from server.AccountManager interface
|
||||||
func (am *MockAccountManager) GetOrCreateAccountByUser(
|
func (am *MockAccountManager) GetOrCreateAccountByUser(
|
||||||
userId, domain string,
|
userId, domain string,
|
||||||
@@ -82,52 +99,22 @@ func (am *MockAccountManager) GetAccountByUser(userId string) (*server.Account,
|
|||||||
return nil, status.Errorf(codes.Unimplemented, "method GetAccountByUser is not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method GetAccountByUser is not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddSetupKey mock implementation of AddSetupKey from server.AccountManager interface
|
// CreateSetupKey mock implementation of CreateSetupKey from server.AccountManager interface
|
||||||
func (am *MockAccountManager) AddSetupKey(
|
func (am *MockAccountManager) CreateSetupKey(
|
||||||
accountId string,
|
accountId string,
|
||||||
keyName string,
|
keyName string,
|
||||||
keyType server.SetupKeyType,
|
keyType server.SetupKeyType,
|
||||||
expiresIn time.Duration,
|
expiresIn time.Duration,
|
||||||
|
autoGroups []string,
|
||||||
) (*server.SetupKey, error) {
|
) (*server.SetupKey, error) {
|
||||||
if am.AddSetupKeyFunc != nil {
|
if am.CreateSetupKeyFunc != nil {
|
||||||
return am.AddSetupKeyFunc(accountId, keyName, keyType, expiresIn)
|
return am.CreateSetupKeyFunc(accountId, keyName, keyType, expiresIn, autoGroups)
|
||||||
}
|
}
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method AddSetupKey is not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method CreateSetupKey is not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
// RevokeSetupKey mock implementation of RevokeSetupKey from server.AccountManager interface
|
// GetAccountByUserOrAccountID mock implementation of GetAccountByUserOrAccountID from server.AccountManager interface
|
||||||
func (am *MockAccountManager) RevokeSetupKey(
|
func (am *MockAccountManager) GetAccountByUserOrAccountID(
|
||||||
accountId string,
|
|
||||||
keyId string,
|
|
||||||
) (*server.SetupKey, error) {
|
|
||||||
if am.RevokeSetupKeyFunc != nil {
|
|
||||||
return am.RevokeSetupKeyFunc(accountId, keyId)
|
|
||||||
}
|
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method RevokeSetupKey is not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
// RenameSetupKey mock implementation of RenameSetupKey from server.AccountManager interface
|
|
||||||
func (am *MockAccountManager) RenameSetupKey(
|
|
||||||
accountId string,
|
|
||||||
keyId string,
|
|
||||||
newName string,
|
|
||||||
) (*server.SetupKey, error) {
|
|
||||||
if am.RenameSetupKeyFunc != nil {
|
|
||||||
return am.RenameSetupKeyFunc(accountId, keyId, newName)
|
|
||||||
}
|
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method RenameSetupKey is not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAccountById mock implementation of GetAccountById from server.AccountManager interface
|
|
||||||
func (am *MockAccountManager) GetAccountById(accountId string) (*server.Account, error) {
|
|
||||||
if am.GetAccountByIdFunc != nil {
|
|
||||||
return am.GetAccountByIdFunc(accountId)
|
|
||||||
}
|
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method GetAccountById is not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAccountByUserOrAccountId mock implementation of GetAccountByUserOrAccountId from server.AccountManager interface
|
|
||||||
func (am *MockAccountManager) GetAccountByUserOrAccountId(
|
|
||||||
userId, accountId, domain string,
|
userId, accountId, domain string,
|
||||||
) (*server.Account, error) {
|
) (*server.Account, error) {
|
||||||
if am.GetAccountByUserOrAccountIdFunc != nil {
|
if am.GetAccountByUserOrAccountIdFunc != nil {
|
||||||
@@ -135,20 +122,7 @@ func (am *MockAccountManager) GetAccountByUserOrAccountId(
|
|||||||
}
|
}
|
||||||
return nil, status.Errorf(
|
return nil, status.Errorf(
|
||||||
codes.Unimplemented,
|
codes.Unimplemented,
|
||||||
"method GetAccountByUserOrAccountId is not implemented",
|
"method GetAccountByUserOrAccountID is not implemented",
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAccountWithAuthorizationClaims mock implementation of GetAccountWithAuthorizationClaims from server.AccountManager interface
|
|
||||||
func (am *MockAccountManager) GetAccountWithAuthorizationClaims(
|
|
||||||
claims jwtclaims.AuthorizationClaims,
|
|
||||||
) (*server.Account, error) {
|
|
||||||
if am.GetAccountWithAuthorizationClaimsFunc != nil {
|
|
||||||
return am.GetAccountWithAuthorizationClaimsFunc(claims)
|
|
||||||
}
|
|
||||||
return nil, status.Errorf(
|
|
||||||
codes.Unimplemented,
|
|
||||||
"method GetAccountWithAuthorizationClaims is not implemented",
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,26 +150,6 @@ func (am *MockAccountManager) MarkPeerConnected(peerKey string, connected bool)
|
|||||||
return status.Errorf(codes.Unimplemented, "method MarkPeerConnected is not implemented")
|
return status.Errorf(codes.Unimplemented, "method MarkPeerConnected is not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
// RenamePeer mock implementation of RenamePeer from server.AccountManager interface
|
|
||||||
func (am *MockAccountManager) RenamePeer(
|
|
||||||
accountId string,
|
|
||||||
peerKey string,
|
|
||||||
newName string,
|
|
||||||
) (*server.Peer, error) {
|
|
||||||
if am.RenamePeerFunc != nil {
|
|
||||||
return am.RenamePeerFunc(accountId, peerKey, newName)
|
|
||||||
}
|
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method RenamePeer is not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeletePeer mock implementation of DeletePeer from server.AccountManager interface
|
|
||||||
func (am *MockAccountManager) DeletePeer(accountId string, peerKey string) (*server.Peer, error) {
|
|
||||||
if am.DeletePeerFunc != nil {
|
|
||||||
return am.DeletePeerFunc(accountId, peerKey)
|
|
||||||
}
|
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method DeletePeer is not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPeerByIP mock implementation of GetPeerByIP from server.AccountManager interface
|
// GetPeerByIP mock implementation of GetPeerByIP from server.AccountManager interface
|
||||||
func (am *MockAccountManager) GetPeerByIP(accountId string, peerIP string) (*server.Peer, error) {
|
func (am *MockAccountManager) GetPeerByIP(accountId string, peerIP string) (*server.Peer, error) {
|
||||||
if am.GetPeerByIPFunc != nil {
|
if am.GetPeerByIPFunc != nil {
|
||||||
@@ -297,9 +251,9 @@ func (am *MockAccountManager) GroupListPeers(accountID, groupID string) ([]*serv
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetRule mock implementation of GetRule from server.AccountManager interface
|
// GetRule mock implementation of GetRule from server.AccountManager interface
|
||||||
func (am *MockAccountManager) GetRule(accountID, ruleID string) (*server.Rule, error) {
|
func (am *MockAccountManager) GetRule(accountID, ruleID, userID string) (*server.Rule, error) {
|
||||||
if am.GetRuleFunc != nil {
|
if am.GetRuleFunc != nil {
|
||||||
return am.GetRuleFunc(accountID, ruleID)
|
return am.GetRuleFunc(accountID, ruleID, userID)
|
||||||
}
|
}
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method GetRule is not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method GetRule is not implemented")
|
||||||
}
|
}
|
||||||
@@ -329,9 +283,9 @@ func (am *MockAccountManager) DeleteRule(accountID, ruleID string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ListRules mock implementation of ListRules from server.AccountManager interface
|
// ListRules mock implementation of ListRules from server.AccountManager interface
|
||||||
func (am *MockAccountManager) ListRules(accountID string) ([]*server.Rule, error) {
|
func (am *MockAccountManager) ListRules(accountID, userID string) ([]*server.Rule, error) {
|
||||||
if am.ListRulesFunc != nil {
|
if am.ListRulesFunc != nil {
|
||||||
return am.ListRulesFunc(accountID)
|
return am.ListRulesFunc(accountID, userID)
|
||||||
}
|
}
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method ListRules is not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method ListRules is not implemented")
|
||||||
}
|
}
|
||||||
@@ -370,16 +324,16 @@ func (am *MockAccountManager) UpdatePeer(accountID string, peer *server.Peer) (*
|
|||||||
|
|
||||||
// CreateRoute mock implementation of CreateRoute from server.AccountManager interface
|
// CreateRoute mock implementation of CreateRoute from server.AccountManager interface
|
||||||
func (am *MockAccountManager) CreateRoute(accountID string, network, peer, description, netID string, masquerade bool, metric int, enabled bool) (*route.Route, error) {
|
func (am *MockAccountManager) CreateRoute(accountID string, network, peer, description, netID string, masquerade bool, metric int, enabled bool) (*route.Route, error) {
|
||||||
if am.GetRouteFunc != nil {
|
if am.CreateRouteFunc != nil {
|
||||||
return am.CreateRouteFunc(accountID, network, peer, description, netID, masquerade, metric, enabled)
|
return am.CreateRouteFunc(accountID, network, peer, description, netID, masquerade, metric, enabled)
|
||||||
}
|
}
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method CreateRoute is not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method CreateRoute is not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRoute mock implementation of GetRoute from server.AccountManager interface
|
// GetRoute mock implementation of GetRoute from server.AccountManager interface
|
||||||
func (am *MockAccountManager) GetRoute(accountID, routeID string) (*route.Route, error) {
|
func (am *MockAccountManager) GetRoute(accountID, routeID, userID string) (*route.Route, error) {
|
||||||
if am.GetRouteFunc != nil {
|
if am.GetRouteFunc != nil {
|
||||||
return am.GetRouteFunc(accountID, routeID)
|
return am.GetRouteFunc(accountID, routeID, userID)
|
||||||
}
|
}
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method GetRoute is not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method GetRoute is not implemented")
|
||||||
}
|
}
|
||||||
@@ -409,9 +363,117 @@ func (am *MockAccountManager) DeleteRoute(accountID, routeID string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ListRoutes mock implementation of ListRoutes from server.AccountManager interface
|
// ListRoutes mock implementation of ListRoutes from server.AccountManager interface
|
||||||
func (am *MockAccountManager) ListRoutes(accountID string) ([]*route.Route, error) {
|
func (am *MockAccountManager) ListRoutes(accountID, userID string) ([]*route.Route, error) {
|
||||||
if am.ListRoutesFunc != nil {
|
if am.ListRoutesFunc != nil {
|
||||||
return am.ListRoutesFunc(accountID)
|
return am.ListRoutesFunc(accountID, userID)
|
||||||
}
|
}
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method ListRoutes is not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method ListRoutes is not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SaveSetupKey mocks SaveSetupKey of the AccountManager interface
|
||||||
|
func (am *MockAccountManager) SaveSetupKey(accountID string, key *server.SetupKey) (*server.SetupKey, error) {
|
||||||
|
if am.SaveSetupKeyFunc != nil {
|
||||||
|
return am.SaveSetupKeyFunc(accountID, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method SaveSetupKey is not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSetupKey mocks GetSetupKey of the AccountManager interface
|
||||||
|
func (am *MockAccountManager) GetSetupKey(accountID, userID, keyID string) (*server.SetupKey, error) {
|
||||||
|
if am.GetSetupKeyFunc != nil {
|
||||||
|
return am.GetSetupKeyFunc(accountID, userID, keyID)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method GetSetupKey is not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListSetupKeys mocks ListSetupKeys of the AccountManager interface
|
||||||
|
func (am *MockAccountManager) ListSetupKeys(accountID, userID string) ([]*server.SetupKey, error) {
|
||||||
|
if am.ListSetupKeysFunc != nil {
|
||||||
|
return am.ListSetupKeysFunc(accountID, userID)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method ListSetupKeys is not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveUser mocks SaveUser of the AccountManager interface
|
||||||
|
func (am *MockAccountManager) SaveUser(accountID string, user *server.User) (*server.UserInfo, error) {
|
||||||
|
if am.SaveUserFunc != nil {
|
||||||
|
return am.SaveUserFunc(accountID, user)
|
||||||
|
}
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method SaveUser is not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNameServerGroup mocks GetNameServerGroup of the AccountManager interface
|
||||||
|
func (am *MockAccountManager) GetNameServerGroup(accountID, nsGroupID string) (*nbdns.NameServerGroup, error) {
|
||||||
|
if am.GetNameServerGroupFunc != nil {
|
||||||
|
return am.GetNameServerGroupFunc(accountID, nsGroupID)
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateNameServerGroup mocks CreateNameServerGroup of the AccountManager interface
|
||||||
|
func (am *MockAccountManager) CreateNameServerGroup(accountID string, name, description string, nameServerList []nbdns.NameServer, groups []string, primary bool, domains []string, enabled bool) (*nbdns.NameServerGroup, error) {
|
||||||
|
if am.CreateNameServerGroupFunc != nil {
|
||||||
|
return am.CreateNameServerGroupFunc(accountID, name, description, nameServerList, groups, primary, domains, enabled)
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveNameServerGroup mocks SaveNameServerGroup of the AccountManager interface
|
||||||
|
func (am *MockAccountManager) SaveNameServerGroup(accountID string, nsGroupToSave *nbdns.NameServerGroup) error {
|
||||||
|
if am.SaveNameServerGroupFunc != nil {
|
||||||
|
return am.SaveNameServerGroupFunc(accountID, nsGroupToSave)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateNameServerGroup mocks UpdateNameServerGroup of the AccountManager interface
|
||||||
|
func (am *MockAccountManager) UpdateNameServerGroup(accountID, nsGroupID string, operations []server.NameServerGroupUpdateOperation) (*nbdns.NameServerGroup, error) {
|
||||||
|
if am.UpdateNameServerGroupFunc != nil {
|
||||||
|
return am.UpdateNameServerGroupFunc(accountID, nsGroupID, operations)
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteNameServerGroup mocks DeleteNameServerGroup of the AccountManager interface
|
||||||
|
func (am *MockAccountManager) DeleteNameServerGroup(accountID, nsGroupID string) error {
|
||||||
|
if am.DeleteNameServerGroupFunc != nil {
|
||||||
|
return am.DeleteNameServerGroupFunc(accountID, nsGroupID)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListNameServerGroups mocks ListNameServerGroups of the AccountManager interface
|
||||||
|
func (am *MockAccountManager) ListNameServerGroups(accountID string) ([]*nbdns.NameServerGroup, error) {
|
||||||
|
if am.ListNameServerGroupsFunc != nil {
|
||||||
|
return am.ListNameServerGroupsFunc(accountID)
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateUser mocks CreateUser of the AccountManager interface
|
||||||
|
func (am *MockAccountManager) CreateUser(accountID string, invite *server.UserInfo) (*server.UserInfo, error) {
|
||||||
|
if am.CreateUserFunc != nil {
|
||||||
|
return am.CreateUserFunc(accountID, invite)
|
||||||
|
}
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method CreateUser is not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAccountFromToken mocks GetAccountFromToken of the AccountManager interface
|
||||||
|
func (am *MockAccountManager) GetAccountFromToken(claims jwtclaims.AuthorizationClaims) (*server.Account, *server.User,
|
||||||
|
error) {
|
||||||
|
if am.GetAccountFromTokenFunc != nil {
|
||||||
|
return am.GetAccountFromTokenFunc(claims)
|
||||||
|
}
|
||||||
|
return nil, nil, status.Errorf(codes.Unimplemented, "method GetAccountFromToken is not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPeers mocks GetPeers of the AccountManager interface
|
||||||
|
func (am *MockAccountManager) GetPeers(accountID, userID string) ([]*server.Peer, error) {
|
||||||
|
if am.GetAccountFromTokenFunc != nil {
|
||||||
|
return am.GetPeersFunc(accountID, userID)
|
||||||
|
}
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method GetPeersFunc is not implemented")
|
||||||
|
}
|
||||||
|
|||||||
409
management/server/nameserver.go
Normal file
409
management/server/nameserver.go
Normal file
@@ -0,0 +1,409 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
nbdns "github.com/netbirdio/netbird/dns"
|
||||||
|
"github.com/netbirdio/netbird/management/server/status"
|
||||||
|
"github.com/rs/xid"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"strconv"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// UpdateNameServerGroupName indicates a nameserver group name update operation
|
||||||
|
UpdateNameServerGroupName NameServerGroupUpdateOperationType = iota
|
||||||
|
// UpdateNameServerGroupDescription indicates a nameserver group description update operation
|
||||||
|
UpdateNameServerGroupDescription
|
||||||
|
// UpdateNameServerGroupNameServers indicates a nameserver group nameservers list update operation
|
||||||
|
UpdateNameServerGroupNameServers
|
||||||
|
// UpdateNameServerGroupGroups indicates a nameserver group' groups update operation
|
||||||
|
UpdateNameServerGroupGroups
|
||||||
|
// UpdateNameServerGroupEnabled indicates a nameserver group status update operation
|
||||||
|
UpdateNameServerGroupEnabled
|
||||||
|
// UpdateNameServerGroupPrimary indicates a nameserver group primary status update operation
|
||||||
|
UpdateNameServerGroupPrimary
|
||||||
|
// UpdateNameServerGroupDomains indicates a nameserver group' domains update operation
|
||||||
|
UpdateNameServerGroupDomains
|
||||||
|
)
|
||||||
|
|
||||||
|
// NameServerGroupUpdateOperationType operation type
|
||||||
|
type NameServerGroupUpdateOperationType int
|
||||||
|
|
||||||
|
func (t NameServerGroupUpdateOperationType) String() string {
|
||||||
|
switch t {
|
||||||
|
case UpdateNameServerGroupDescription:
|
||||||
|
return "UpdateNameServerGroupDescription"
|
||||||
|
case UpdateNameServerGroupName:
|
||||||
|
return "UpdateNameServerGroupName"
|
||||||
|
case UpdateNameServerGroupNameServers:
|
||||||
|
return "UpdateNameServerGroupNameServers"
|
||||||
|
case UpdateNameServerGroupGroups:
|
||||||
|
return "UpdateNameServerGroupGroups"
|
||||||
|
case UpdateNameServerGroupEnabled:
|
||||||
|
return "UpdateNameServerGroupEnabled"
|
||||||
|
case UpdateNameServerGroupPrimary:
|
||||||
|
return "UpdateNameServerGroupPrimary"
|
||||||
|
case UpdateNameServerGroupDomains:
|
||||||
|
return "UpdateNameServerGroupDomains"
|
||||||
|
default:
|
||||||
|
return "InvalidOperation"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NameServerGroupUpdateOperation operation object with type and values to be applied
|
||||||
|
type NameServerGroupUpdateOperation struct {
|
||||||
|
Type NameServerGroupUpdateOperationType
|
||||||
|
Values []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNameServerGroup gets a nameserver group object from account and nameserver group IDs
|
||||||
|
func (am *DefaultAccountManager) GetNameServerGroup(accountID, nsGroupID string) (*nbdns.NameServerGroup, error) {
|
||||||
|
|
||||||
|
unlock := am.Store.AcquireAccountLock(accountID)
|
||||||
|
defer unlock()
|
||||||
|
|
||||||
|
account, err := am.Store.GetAccount(accountID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
nsGroup, found := account.NameServerGroups[nsGroupID]
|
||||||
|
if found {
|
||||||
|
return nsGroup.Copy(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, status.Errorf(status.NotFound, "nameserver group with ID %s not found", nsGroupID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateNameServerGroup creates and saves a new nameserver group
|
||||||
|
func (am *DefaultAccountManager) CreateNameServerGroup(accountID string, name, description string, nameServerList []nbdns.NameServer, groups []string, primary bool, domains []string, enabled bool) (*nbdns.NameServerGroup, error) {
|
||||||
|
|
||||||
|
unlock := am.Store.AcquireAccountLock(accountID)
|
||||||
|
defer unlock()
|
||||||
|
|
||||||
|
account, err := am.Store.GetAccount(accountID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
newNSGroup := &nbdns.NameServerGroup{
|
||||||
|
ID: xid.New().String(),
|
||||||
|
Name: name,
|
||||||
|
Description: description,
|
||||||
|
NameServers: nameServerList,
|
||||||
|
Groups: groups,
|
||||||
|
Enabled: enabled,
|
||||||
|
Primary: primary,
|
||||||
|
Domains: domains,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = validateNameServerGroup(false, newNSGroup, account)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if account.NameServerGroups == nil {
|
||||||
|
account.NameServerGroups = make(map[string]*nbdns.NameServerGroup)
|
||||||
|
}
|
||||||
|
|
||||||
|
account.NameServerGroups[newNSGroup.ID] = newNSGroup
|
||||||
|
|
||||||
|
account.Network.IncSerial()
|
||||||
|
err = am.Store.SaveAccount(account)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = am.updateAccountPeers(account)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
return newNSGroup.Copy(), status.Errorf(status.Internal, "failed to update peers after create nameserver %s", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return newNSGroup.Copy(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveNameServerGroup saves nameserver group
|
||||||
|
func (am *DefaultAccountManager) SaveNameServerGroup(accountID string, nsGroupToSave *nbdns.NameServerGroup) error {
|
||||||
|
|
||||||
|
unlock := am.Store.AcquireAccountLock(accountID)
|
||||||
|
defer unlock()
|
||||||
|
|
||||||
|
if nsGroupToSave == nil {
|
||||||
|
return status.Errorf(status.InvalidArgument, "nameserver group provided is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
account, err := am.Store.GetAccount(accountID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = validateNameServerGroup(true, nsGroupToSave, account)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
account.NameServerGroups[nsGroupToSave.ID] = nsGroupToSave
|
||||||
|
|
||||||
|
account.Network.IncSerial()
|
||||||
|
err = am.Store.SaveAccount(account)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = am.updateAccountPeers(account)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
return status.Errorf(status.Internal, "failed to update peers after update nameserver %s", nsGroupToSave.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateNameServerGroup updates existing nameserver group with set of operations
|
||||||
|
func (am *DefaultAccountManager) UpdateNameServerGroup(accountID, nsGroupID string, operations []NameServerGroupUpdateOperation) (*nbdns.NameServerGroup, error) {
|
||||||
|
|
||||||
|
unlock := am.Store.AcquireAccountLock(accountID)
|
||||||
|
defer unlock()
|
||||||
|
|
||||||
|
account, err := am.Store.GetAccount(accountID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(operations) == 0 {
|
||||||
|
return nil, status.Errorf(status.InvalidArgument, "operations shouldn't be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
nsGroupToUpdate, ok := account.NameServerGroups[nsGroupID]
|
||||||
|
if !ok {
|
||||||
|
return nil, status.Errorf(status.NotFound, "nameserver group ID %s no longer exists", nsGroupID)
|
||||||
|
}
|
||||||
|
|
||||||
|
newNSGroup := nsGroupToUpdate.Copy()
|
||||||
|
|
||||||
|
for _, operation := range operations {
|
||||||
|
valuesCount := len(operation.Values)
|
||||||
|
if valuesCount < 1 {
|
||||||
|
return nil, status.Errorf(status.InvalidArgument, "operation %s contains invalid number of values, it should be at least 1", operation.Type.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, value := range operation.Values {
|
||||||
|
if value == "" {
|
||||||
|
return nil, status.Errorf(status.InvalidArgument, "operation %s contains invalid empty string value", operation.Type.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch operation.Type {
|
||||||
|
case UpdateNameServerGroupDescription:
|
||||||
|
newNSGroup.Description = operation.Values[0]
|
||||||
|
case UpdateNameServerGroupName:
|
||||||
|
if valuesCount > 1 {
|
||||||
|
return nil, status.Errorf(status.InvalidArgument, "failed to parse name values, expected 1 value got %d", valuesCount)
|
||||||
|
}
|
||||||
|
err = validateNSGroupName(operation.Values[0], nsGroupID, account.NameServerGroups)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
newNSGroup.Name = operation.Values[0]
|
||||||
|
case UpdateNameServerGroupNameServers:
|
||||||
|
var nsList []nbdns.NameServer
|
||||||
|
for _, url := range operation.Values {
|
||||||
|
ns, err := nbdns.ParseNameServerURL(url)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
nsList = append(nsList, ns)
|
||||||
|
}
|
||||||
|
err = validateNSList(nsList)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
newNSGroup.NameServers = nsList
|
||||||
|
case UpdateNameServerGroupGroups:
|
||||||
|
err = validateGroups(operation.Values, account.Groups)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
newNSGroup.Groups = operation.Values
|
||||||
|
case UpdateNameServerGroupEnabled:
|
||||||
|
enabled, err := strconv.ParseBool(operation.Values[0])
|
||||||
|
if err != nil {
|
||||||
|
return nil, status.Errorf(status.InvalidArgument, "failed to parse enabled %s, not boolean", operation.Values[0])
|
||||||
|
}
|
||||||
|
newNSGroup.Enabled = enabled
|
||||||
|
case UpdateNameServerGroupPrimary:
|
||||||
|
primary, err := strconv.ParseBool(operation.Values[0])
|
||||||
|
if err != nil {
|
||||||
|
return nil, status.Errorf(status.InvalidArgument, "failed to parse primary status %s, not boolean", operation.Values[0])
|
||||||
|
}
|
||||||
|
newNSGroup.Primary = primary
|
||||||
|
case UpdateNameServerGroupDomains:
|
||||||
|
err = validateDomainInput(false, operation.Values)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
newNSGroup.Domains = operation.Values
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
account.NameServerGroups[nsGroupID] = newNSGroup
|
||||||
|
|
||||||
|
account.Network.IncSerial()
|
||||||
|
err = am.Store.SaveAccount(account)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = am.updateAccountPeers(account)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
return newNSGroup.Copy(), status.Errorf(status.Internal, "failed to update peers after update nameserver %s", newNSGroup.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return newNSGroup.Copy(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteNameServerGroup deletes nameserver group with nsGroupID
|
||||||
|
func (am *DefaultAccountManager) DeleteNameServerGroup(accountID, nsGroupID string) error {
|
||||||
|
|
||||||
|
unlock := am.Store.AcquireAccountLock(accountID)
|
||||||
|
defer unlock()
|
||||||
|
|
||||||
|
account, err := am.Store.GetAccount(accountID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(account.NameServerGroups, nsGroupID)
|
||||||
|
|
||||||
|
account.Network.IncSerial()
|
||||||
|
err = am.Store.SaveAccount(account)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = am.updateAccountPeers(account)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
return status.Errorf(status.Internal, "failed to update peers after deleting nameserver %s", nsGroupID)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListNameServerGroups returns a list of nameserver groups from account
|
||||||
|
func (am *DefaultAccountManager) ListNameServerGroups(accountID string) ([]*nbdns.NameServerGroup, error) {
|
||||||
|
|
||||||
|
unlock := am.Store.AcquireAccountLock(accountID)
|
||||||
|
defer unlock()
|
||||||
|
|
||||||
|
account, err := am.Store.GetAccount(accountID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
nsGroups := make([]*nbdns.NameServerGroup, 0, len(account.NameServerGroups))
|
||||||
|
for _, item := range account.NameServerGroups {
|
||||||
|
nsGroups = append(nsGroups, item.Copy())
|
||||||
|
}
|
||||||
|
|
||||||
|
return nsGroups, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateNameServerGroup(existingGroup bool, nameserverGroup *nbdns.NameServerGroup, account *Account) error {
|
||||||
|
nsGroupID := ""
|
||||||
|
if existingGroup {
|
||||||
|
nsGroupID = nameserverGroup.ID
|
||||||
|
_, found := account.NameServerGroups[nsGroupID]
|
||||||
|
if !found {
|
||||||
|
return status.Errorf(status.NotFound, "nameserver group with ID %s was not found", nsGroupID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err := validateDomainInput(nameserverGroup.Primary, nameserverGroup.Domains)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = validateNSGroupName(nameserverGroup.Name, nsGroupID, account.NameServerGroups)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = validateNSList(nameserverGroup.NameServers)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = validateGroups(nameserverGroup.Groups, account.Groups)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateDomainInput(primary bool, domains []string) error {
|
||||||
|
if !primary && len(domains) == 0 {
|
||||||
|
return status.Errorf(status.InvalidArgument, "nameserver group primary status is false and domains are empty,"+
|
||||||
|
" it should be primary or have at least one domain")
|
||||||
|
}
|
||||||
|
if primary && len(domains) != 0 {
|
||||||
|
return status.Errorf(status.InvalidArgument, "nameserver group primary status is true and domains are not empty,"+
|
||||||
|
" you should set either primary or domain")
|
||||||
|
}
|
||||||
|
for _, domain := range domains {
|
||||||
|
_, valid := dns.IsDomainName(domain)
|
||||||
|
if !valid {
|
||||||
|
return status.Errorf(status.InvalidArgument, "nameserver group got an invalid domain: %s", domain)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateNSGroupName(name, nsGroupID string, nsGroupMap map[string]*nbdns.NameServerGroup) error {
|
||||||
|
if utf8.RuneCountInString(name) > nbdns.MaxGroupNameChar || name == "" {
|
||||||
|
return status.Errorf(status.InvalidArgument, "nameserver group name should be between 1 and %d", nbdns.MaxGroupNameChar)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, nsGroup := range nsGroupMap {
|
||||||
|
if name == nsGroup.Name && nsGroup.ID != nsGroupID {
|
||||||
|
return status.Errorf(status.InvalidArgument, "a nameserver group with name %s already exist", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateNSList(list []nbdns.NameServer) error {
|
||||||
|
nsListLenght := len(list)
|
||||||
|
if nsListLenght == 0 || nsListLenght > 2 {
|
||||||
|
return status.Errorf(status.InvalidArgument, "the list of nameservers should be 1 or 2, got %d", len(list))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateGroups(list []string, groups map[string]*Group) error {
|
||||||
|
if len(list) == 0 {
|
||||||
|
return status.Errorf(status.InvalidArgument, "the list of group IDs should not be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, id := range list {
|
||||||
|
if id == "" {
|
||||||
|
return status.Errorf(status.InvalidArgument, "group ID should not be empty string")
|
||||||
|
}
|
||||||
|
found := false
|
||||||
|
for groupID := range groups {
|
||||||
|
if id == groupID {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
return status.Errorf(status.InvalidArgument, "group id %s not found", id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
1156
management/server/nameserver_test.go
Normal file
1156
management/server/nameserver_test.go
Normal file
File diff suppressed because it is too large
Load Diff
@@ -2,10 +2,10 @@ package server
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/c-robinson/iplib"
|
"github.com/c-robinson/iplib"
|
||||||
|
nbdns "github.com/netbirdio/netbird/dns"
|
||||||
|
"github.com/netbirdio/netbird/management/server/status"
|
||||||
"github.com/netbirdio/netbird/route"
|
"github.com/netbirdio/netbird/route"
|
||||||
"github.com/rs/xid"
|
"github.com/rs/xid"
|
||||||
"google.golang.org/grpc/codes"
|
|
||||||
"google.golang.org/grpc/status"
|
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -23,9 +23,10 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type NetworkMap struct {
|
type NetworkMap struct {
|
||||||
Peers []*Peer
|
Peers []*Peer
|
||||||
Network *Network
|
Network *Network
|
||||||
Routes []*route.Route
|
Routes []*route.Route
|
||||||
|
DNSConfig nbdns.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
type Network struct {
|
type Network struct {
|
||||||
@@ -93,7 +94,7 @@ func AllocatePeerIP(ipNet net.IPNet, takenIps []net.IP) (net.IP, error) {
|
|||||||
ips, _ := generateIPs(&ipNet, takenIPMap)
|
ips, _ := generateIPs(&ipNet, takenIPMap)
|
||||||
|
|
||||||
if len(ips) == 0 {
|
if len(ips) == 0 {
|
||||||
return nil, status.Errorf(codes.OutOfRange, "failed allocating new IP for the ipNet %s - network is out of IPs", ipNet.String())
|
return nil, status.Errorf(status.PreconditionFailed, "failed allocating new IP for the ipNet %s - network is out of IPs", ipNet.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
// pick a random IP
|
// pick a random IP
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user