Compare commits
29 Commits
feature/pe
...
refactor-d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0c6c5fdc70 | ||
|
|
27c3a4c5d6 | ||
|
|
f31b06fc92 | ||
|
|
9d213e0b54 | ||
|
|
5dde044fa5 | ||
|
|
5a3d9e401f | ||
|
|
fde1a2196c | ||
|
|
0aeb87742a | ||
|
|
6d747b2f83 | ||
|
|
199bf73103 | ||
|
|
17f5abc653 | ||
|
|
aa935bdae3 | ||
|
|
452419c4c3 | ||
|
|
17b1099032 | ||
|
|
a4b9e93217 | ||
|
|
63d7957140 | ||
|
|
9a6814deff | ||
|
|
190698bcf2 | ||
|
|
468fa2940b | ||
|
|
79a0647a26 | ||
|
|
17ceb3bde8 | ||
|
|
5a8f1763a6 | ||
|
|
f64e73ca70 | ||
|
|
b085419ab8 | ||
|
|
d78b652ff7 | ||
|
|
7251150c1c | ||
|
|
b65c2f69b0 | ||
|
|
d8ce08d898 | ||
|
|
e1c50248d9 |
2
.github/ISSUE_TEMPLATE/bug-issue-report.md
vendored
@@ -2,7 +2,7 @@
|
|||||||
name: Bug/Issue report
|
name: Bug/Issue report
|
||||||
about: Create a report to help us improve
|
about: Create a report to help us improve
|
||||||
title: ''
|
title: ''
|
||||||
labels: ['triage']
|
labels: ['triage-needed']
|
||||||
assignees: ''
|
assignees: ''
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -162,6 +162,13 @@ jobs:
|
|||||||
test $count -eq 4
|
test $count -eq 4
|
||||||
working-directory: infrastructure_files/artifacts
|
working-directory: infrastructure_files/artifacts
|
||||||
|
|
||||||
|
- name: test geolocation databases
|
||||||
|
working-directory: infrastructure_files/artifacts
|
||||||
|
run: |
|
||||||
|
sleep 30
|
||||||
|
docker compose exec management ls -l /var/lib/netbird/ | grep -i GeoLite2-City.mmdb
|
||||||
|
docker compose exec management ls -l /var/lib/netbird/ | grep -i geonames.db
|
||||||
|
|
||||||
test-getting-started-script:
|
test-getting-started-script:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
@@ -199,6 +206,6 @@ jobs:
|
|||||||
- name: test script
|
- name: test script
|
||||||
run: bash -x infrastructure_files/download-geolite2.sh
|
run: bash -x infrastructure_files/download-geolite2.sh
|
||||||
- name: test mmdb file exists
|
- name: test mmdb file exists
|
||||||
run: ls -l GeoLite2-City_*/GeoLite2-City.mmdb
|
run: test -f GeoLite2-City.mmdb
|
||||||
- name: test geonames file exists
|
- name: test geonames file exists
|
||||||
run: test -f geonames.db
|
run: test -f geonames.db
|
||||||
|
|||||||
@@ -63,6 +63,14 @@ linters-settings:
|
|||||||
enable:
|
enable:
|
||||||
- nilness
|
- nilness
|
||||||
|
|
||||||
|
revive:
|
||||||
|
rules:
|
||||||
|
- name: exported
|
||||||
|
severity: warning
|
||||||
|
disabled: false
|
||||||
|
arguments:
|
||||||
|
- "checkPrivateReceivers"
|
||||||
|
- "sayRepetitiveInsteadOfStutters"
|
||||||
tenv:
|
tenv:
|
||||||
# The option `all` will run against whole test files (`_test.go`) regardless of method/function signatures.
|
# The option `all` will run against whole test files (`_test.go`) regardless of method/function signatures.
|
||||||
# Otherwise, only methods that take `*testing.T`, `*testing.B`, and `testing.TB` as arguments are checked.
|
# Otherwise, only methods that take `*testing.T`, `*testing.B`, and `testing.TB` as arguments are checked.
|
||||||
@@ -93,6 +101,7 @@ linters:
|
|||||||
- nilerr # finds the code that returns nil even if it checks that the error is not nil
|
- nilerr # finds the code that returns nil even if it checks that the error is not nil
|
||||||
- nilnil # checks that there is no simultaneous return of nil error and an invalid value
|
- nilnil # checks that there is no simultaneous return of nil error and an invalid value
|
||||||
- predeclared # predeclared finds code that shadows one of Go's predeclared identifiers
|
- predeclared # predeclared finds code that shadows one of Go's predeclared identifiers
|
||||||
|
- revive # Fast, configurable, extensible, flexible, and beautiful linter for Go. Drop-in replacement of golint.
|
||||||
- sqlclosecheck # checks that sql.Rows and sql.Stmt are closed
|
- sqlclosecheck # checks that sql.Rows and sql.Stmt are closed
|
||||||
- thelper # thelper detects Go test helpers without t.Helper() call and checks the consistency of test helpers.
|
- thelper # thelper detects Go test helpers without t.Helper() call and checks the consistency of test helpers.
|
||||||
- wastedassign # wastedassign finds wasted assignment statements
|
- wastedassign # wastedassign finds wasted assignment statements
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ nfpms:
|
|||||||
contents:
|
contents:
|
||||||
- src: client/ui/netbird.desktop
|
- src: client/ui/netbird.desktop
|
||||||
dst: /usr/share/applications/netbird.desktop
|
dst: /usr/share/applications/netbird.desktop
|
||||||
- src: client/ui/netbird-systemtray-default.png
|
- src: client/ui/netbird-systemtray-connected.png
|
||||||
dst: /usr/share/pixmaps/netbird.png
|
dst: /usr/share/pixmaps/netbird.png
|
||||||
dependencies:
|
dependencies:
|
||||||
- netbird
|
- netbird
|
||||||
@@ -71,7 +71,7 @@ nfpms:
|
|||||||
contents:
|
contents:
|
||||||
- src: client/ui/netbird.desktop
|
- src: client/ui/netbird.desktop
|
||||||
dst: /usr/share/applications/netbird.desktop
|
dst: /usr/share/applications/netbird.desktop
|
||||||
- src: client/ui/netbird-systemtray-default.png
|
- src: client/ui/netbird-systemtray-connected.png
|
||||||
dst: /usr/share/pixmaps/netbird.png
|
dst: /usr/share/pixmaps/netbird.png
|
||||||
dependencies:
|
dependencies:
|
||||||
- netbird
|
- netbird
|
||||||
|
|||||||
33
README.md
@@ -1,6 +1,6 @@
|
|||||||
<p align="center">
|
<p align="center">
|
||||||
<strong>:hatching_chick: New Release! Self-hosting in under 5 min.</strong>
|
<strong>:hatching_chick: New Release! Device Posture Checks.</strong>
|
||||||
<a href="https://github.com/netbirdio/netbird#quickstart-with-self-hosted-netbird">
|
<a href="https://docs.netbird.io/how-to/manage-posture-checks">
|
||||||
Learn more
|
Learn more
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
@@ -42,25 +42,22 @@
|
|||||||
|
|
||||||
**Secure.** NetBird enables secure remote access by applying granular access policies, while allowing you to manage them intuitively from a single place. Works universally on any infrastructure.
|
**Secure.** NetBird enables secure remote access by applying granular access policies, while allowing you to manage them intuitively from a single place. Works universally on any infrastructure.
|
||||||
|
|
||||||
### Secure peer-to-peer VPN with SSO and MFA in minutes
|
### Open-Source Network Security in a Single Platform
|
||||||
|
|
||||||
https://user-images.githubusercontent.com/700848/197345890-2e2cded5-7b7a-436f-a444-94e80dd24f46.mov
|

|
||||||
|
|
||||||
### Key features
|
### Key features
|
||||||
|
|
||||||
| Connectivity | Management | Automation | Platforms |
|
| Connectivity | Management | Security | Automation | Platforms |
|
||||||
|---------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------|----------------------------------------------------------------------------|---------------------------------------|
|
|------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------|
|
||||||
| <ul><li> - \[x] Kernel WireGuard </ul></li> | <ul><li> - \[x] [Admin Web UI](https://github.com/netbirdio/dashboard) </ul></li> | <ul><li> - \[x] [Public API](https://docs.netbird.io/api) </ul></li> | <ul><li> - \[x] Linux </ul></li> |
|
| <ul><li> - \[x] Kernel WireGuard </ul></li> | <ul><li> - \[x] [Admin Web UI](https://github.com/netbirdio/dashboard) </ul></li> | <ul><li> - \[x] [SSO & MFA support](https://docs.netbird.io/how-to/installation#running-net-bird-with-sso-login) </ul></li> | <ul><li> - \[x] [Public API](https://docs.netbird.io/api) </ul></li> | <ul><li> - \[x] Linux </ul></li> |
|
||||||
| <ul><li> - \[x] Peer-to-peer connections </ul></li> | <ul><li> - \[x] Auto peer discovery and configuration </ul></li> | <ul><li> - \[x] [Setup keys for bulk network provisioning](https://docs.netbird.io/how-to/register-machines-using-setup-keys) </ul></li> | <ul><li> - \[x] Mac </ul></li> |
|
| <ul><li> - \[x] Peer-to-peer connections </ul></li> | <ul><li> - \[x] Auto peer discovery and configuration </ul></li> | <ul><li> - \[x] [Access control - groups & rules](https://docs.netbird.io/how-to/manage-network-access) </ul></li> | <ul><li> - \[x] [Setup keys for bulk network provisioning](https://docs.netbird.io/how-to/register-machines-using-setup-keys) </ul></li> | <ul><li> - \[x] Mac </ul></li> |
|
||||||
| <ul><li> - \[x] Peer-to-peer encryption </ul></li> | <ul><li> - \[x] [IdP integrations](https://docs.netbird.io/selfhosted/identity-providers) </ul></li> | <ul><li> - \[x] [Self-hosting quickstart script](https://docs.netbird.io/selfhosted/selfhosted-quickstart) </ul></li> | <ul><li> - \[x] Windows </ul></li> |
|
| <ul><li> - \[x] Connection relay fallback </ul></li> | <ul><li> - \[x] [IdP integrations](https://docs.netbird.io/selfhosted/identity-providers) </ul></li> | <ul><li> - \[x] [Activity logging](https://docs.netbird.io/how-to/monitor-system-and-network-activity) </ul></li> | <ul><li> - \[x] [Self-hosting quickstart script](https://docs.netbird.io/selfhosted/selfhosted-quickstart) </ul></li> | <ul><li> - \[x] Windows </ul></li> |
|
||||||
| <ul><li> - \[x] Connection relay fallback </ul></li> | <ul><li> - \[x] [SSO & MFA support](https://docs.netbird.io/how-to/installation#running-net-bird-with-sso-login) </ul></li> | <ul><li> - \[x] IdP groups sync with JWT </ul></li> | <ul><li> - \[x] Android </ul></li> |
|
| <ul><li> - \[x] [Routes to external networks](https://docs.netbird.io/how-to/routing-traffic-to-private-networks) </ul></li> | <ul><li> - \[x] [Private DNS](https://docs.netbird.io/how-to/manage-dns-in-your-network) </ul></li> | <ul><li> - \[x] [Device posture checks](https://docs.netbird.io/how-to/manage-posture-checks) </ul></li> | <ul><li> - \[x] IdP groups sync with JWT </ul></li> | <ul><li> - \[x] Android </ul></li> |
|
||||||
| <ul><li> - \[x] [Routes to external networks](https://docs.netbird.io/how-to/routing-traffic-to-private-networks) </ul></li> | <ul><li> - \[x] [Access control - groups & rules](https://docs.netbird.io/how-to/manage-network-access) </ul></li> | | <ul><li> - \[x] iOS </ul></li> |
|
| <ul><li> - \[x] NAT traversal with BPF </ul></li> | <ul><li> - \[x] [Multiuser support](https://docs.netbird.io/how-to/add-users-to-your-network) </ul></li> | <ul><li> - \[x] Peer-to-peer encryption </ul></li> | | <ul><li> - \[x] iOS </ul></li> |
|
||||||
| <ul><li> - \[x] NAT traversal with BPF </ul></li> | <ul><li> - \[x] [Private DNS](https://docs.netbird.io/how-to/manage-dns-in-your-network) </ul></li> | | <ul><li> - \[x] Docker </ul></li> |
|
| | | <ul><li> - \[x] [Quantum-resistance with Rosenpass](https://netbird.io/knowledge-hub/the-first-quantum-resistant-mesh-vpn) </ul></li> | | <ul><li> - \[x] OpenWRT </ul></li> |
|
||||||
| <ul><li> - \[x] Post-quantum-secure connection through [Rosenpass](https://rosenpass.eu) </ul></li> | <ul><li> - \[x] [Multiuser support](https://docs.netbird.io/how-to/add-users-to-your-network) </ul></li> | | <ul><li> - \[x] OpenWRT </ul></li> |
|
| | | <ui><li> - \[x] [Periodic re-authentication](https://docs.netbird.io/how-to/enforce-periodic-user-authentication)</ul></li> | | <ul><li> - \[x] [Serverless](https://docs.netbird.io/how-to/netbird-on-faas) </ul></li> |
|
||||||
| | <ul><li> - \[x] [Activity logging](https://docs.netbird.io/how-to/monitor-system-and-network-activity) </ul></li> | | |
|
| | | | | <ul><li> - \[x] Docker </ul></li> |
|
||||||
| | <ul><li> - \[x] SSH access management </ul></li> | | |
|
|
||||||
|
|
||||||
|
|
||||||
### Quickstart with NetBird Cloud
|
### Quickstart with NetBird Cloud
|
||||||
|
|
||||||
- Download and install NetBird at [https://app.netbird.io/install](https://app.netbird.io/install)
|
- Download and install NetBird at [https://app.netbird.io/install](https://app.netbird.io/install)
|
||||||
@@ -109,8 +106,8 @@ export NETBIRD_DOMAIN=netbird.example.com; curl -fsSL https://github.com/netbird
|
|||||||
See a complete [architecture overview](https://docs.netbird.io/about-netbird/how-netbird-works#architecture) for details.
|
See a complete [architecture overview](https://docs.netbird.io/about-netbird/how-netbird-works#architecture) for details.
|
||||||
|
|
||||||
### Community projects
|
### Community projects
|
||||||
- [NetBird on OpenWRT](https://github.com/messense/openwrt-netbird)
|
|
||||||
- [NetBird installer script](https://github.com/physk/netbird-installer)
|
- [NetBird installer script](https://github.com/physk/netbird-installer)
|
||||||
|
- [NetBird ansible collection by Dominion Solutions](https://galaxy.ansible.com/ui/repo/published/dominion_solutions/netbird/)
|
||||||
|
|
||||||
**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).
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ type HTTPClient interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// AuthFlowInfo holds information for the OAuth 2.0 authorization flow
|
// AuthFlowInfo holds information for the OAuth 2.0 authorization flow
|
||||||
type AuthFlowInfo struct {
|
type AuthFlowInfo struct { //nolint:revive
|
||||||
DeviceCode string `json:"device_code"`
|
DeviceCode string `json:"device_code"`
|
||||||
UserCode string `json:"user_code"`
|
UserCode string `json:"user_code"`
|
||||||
VerificationURI string `json:"verification_uri"`
|
VerificationURI string `json:"verification_uri"`
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
@@ -23,10 +24,16 @@ const (
|
|||||||
fileMaxNumberOfSearchDomains = 6
|
fileMaxNumberOfSearchDomains = 6
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
dnsFailoverTimeout = 4 * time.Second
|
||||||
|
dnsFailoverAttempts = 1
|
||||||
|
)
|
||||||
|
|
||||||
type fileConfigurator struct {
|
type fileConfigurator struct {
|
||||||
repair *repair
|
repair *repair
|
||||||
|
|
||||||
originalPerms os.FileMode
|
originalPerms os.FileMode
|
||||||
|
nbNameserverIP string
|
||||||
}
|
}
|
||||||
|
|
||||||
func newFileConfigurator() (hostManager, error) {
|
func newFileConfigurator() (hostManager, error) {
|
||||||
@@ -64,7 +71,7 @@ func (f *fileConfigurator) applyDNSConfig(config HostDNSConfig) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
nbSearchDomains := searchDomains(config)
|
nbSearchDomains := searchDomains(config)
|
||||||
nbNameserverIP := config.ServerIP
|
f.nbNameserverIP = config.ServerIP
|
||||||
|
|
||||||
resolvConf, err := parseBackupResolvConf()
|
resolvConf, err := parseBackupResolvConf()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -73,11 +80,11 @@ func (f *fileConfigurator) applyDNSConfig(config HostDNSConfig) error {
|
|||||||
|
|
||||||
f.repair.stopWatchFileChanges()
|
f.repair.stopWatchFileChanges()
|
||||||
|
|
||||||
err = f.updateConfig(nbSearchDomains, nbNameserverIP, resolvConf)
|
err = f.updateConfig(nbSearchDomains, f.nbNameserverIP, resolvConf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
f.repair.watchFileChanges(nbSearchDomains, nbNameserverIP)
|
f.repair.watchFileChanges(nbSearchDomains, f.nbNameserverIP)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,10 +92,11 @@ func (f *fileConfigurator) updateConfig(nbSearchDomains []string, nbNameserverIP
|
|||||||
searchDomainList := mergeSearchDomains(nbSearchDomains, cfg.searchDomains)
|
searchDomainList := mergeSearchDomains(nbSearchDomains, cfg.searchDomains)
|
||||||
nameServers := generateNsList(nbNameserverIP, cfg)
|
nameServers := generateNsList(nbNameserverIP, cfg)
|
||||||
|
|
||||||
|
options := prepareOptionsWithTimeout(cfg.others, int(dnsFailoverTimeout.Seconds()), dnsFailoverAttempts)
|
||||||
buf := prepareResolvConfContent(
|
buf := prepareResolvConfContent(
|
||||||
searchDomainList,
|
searchDomainList,
|
||||||
nameServers,
|
nameServers,
|
||||||
cfg.others)
|
options)
|
||||||
|
|
||||||
log.Debugf("creating managed file %s", defaultResolvConfPath)
|
log.Debugf("creating managed file %s", defaultResolvConfPath)
|
||||||
err := os.WriteFile(defaultResolvConfPath, buf.Bytes(), f.originalPerms)
|
err := os.WriteFile(defaultResolvConfPath, buf.Bytes(), f.originalPerms)
|
||||||
@@ -131,7 +139,12 @@ func (f *fileConfigurator) backup() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f *fileConfigurator) restore() error {
|
func (f *fileConfigurator) restore() error {
|
||||||
err := copyFile(fileDefaultResolvConfBackupLocation, defaultResolvConfPath)
|
err := removeFirstNbNameserver(fileDefaultResolvConfBackupLocation, f.nbNameserverIP)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Failed to remove netbird nameserver from %s on backup restore: %s", fileDefaultResolvConfBackupLocation, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = copyFile(fileDefaultResolvConfBackupLocation, defaultResolvConfPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("restoring %s from %s: %w", defaultResolvConfPath, fileDefaultResolvConfBackupLocation, err)
|
return fmt.Errorf("restoring %s from %s: %w", defaultResolvConfPath, fileDefaultResolvConfBackupLocation, err)
|
||||||
}
|
}
|
||||||
@@ -157,7 +170,7 @@ func (f *fileConfigurator) restoreUncleanShutdownDNS(storedDNSAddress *netip.Add
|
|||||||
currentDNSAddress, err := netip.ParseAddr(resolvConf.nameServers[0])
|
currentDNSAddress, err := netip.ParseAddr(resolvConf.nameServers[0])
|
||||||
// not a valid first nameserver -> restore
|
// not a valid first nameserver -> restore
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("restoring unclean shutdown: parse dns address %s failed: %s", resolvConf.nameServers[1], err)
|
log.Errorf("restoring unclean shutdown: parse dns address %s failed: %s", resolvConf.nameServers[0], err)
|
||||||
return restoreResolvConfFile()
|
return restoreResolvConfFile()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ package dns
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
@@ -14,6 +15,9 @@ const (
|
|||||||
defaultResolvConfPath = "/etc/resolv.conf"
|
defaultResolvConfPath = "/etc/resolv.conf"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var timeoutRegex = regexp.MustCompile(`timeout:\d+`)
|
||||||
|
var attemptsRegex = regexp.MustCompile(`attempts:\d+`)
|
||||||
|
|
||||||
type resolvConf struct {
|
type resolvConf struct {
|
||||||
nameServers []string
|
nameServers []string
|
||||||
searchDomains []string
|
searchDomains []string
|
||||||
@@ -103,3 +107,62 @@ func parseResolvConfFile(resolvConfFile string) (*resolvConf, error) {
|
|||||||
}
|
}
|
||||||
return rconf, nil
|
return rconf, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// prepareOptionsWithTimeout appends timeout to existing options if it doesn't exist,
|
||||||
|
// otherwise it adds a new option with timeout and attempts.
|
||||||
|
func prepareOptionsWithTimeout(input []string, timeout int, attempts int) []string {
|
||||||
|
configs := make([]string, len(input))
|
||||||
|
copy(configs, input)
|
||||||
|
|
||||||
|
for i, config := range configs {
|
||||||
|
if strings.HasPrefix(config, "options") {
|
||||||
|
config = strings.ReplaceAll(config, "rotate", "")
|
||||||
|
config = strings.Join(strings.Fields(config), " ")
|
||||||
|
|
||||||
|
if strings.Contains(config, "timeout:") {
|
||||||
|
config = timeoutRegex.ReplaceAllString(config, fmt.Sprintf("timeout:%d", timeout))
|
||||||
|
} else {
|
||||||
|
config = strings.Replace(config, "options ", fmt.Sprintf("options timeout:%d ", timeout), 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.Contains(config, "attempts:") {
|
||||||
|
config = attemptsRegex.ReplaceAllString(config, fmt.Sprintf("attempts:%d", attempts))
|
||||||
|
} else {
|
||||||
|
config = strings.Replace(config, "options ", fmt.Sprintf("options attempts:%d ", attempts), 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
configs[i] = config
|
||||||
|
return configs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return append(configs, fmt.Sprintf("options timeout:%d attempts:%d", timeout, attempts))
|
||||||
|
}
|
||||||
|
|
||||||
|
// removeFirstNbNameserver removes the given nameserver from the given file if it is in the first position
|
||||||
|
// and writes the file back to the original location
|
||||||
|
func removeFirstNbNameserver(filename, nameserverIP string) error {
|
||||||
|
resolvConf, err := parseResolvConfFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("parse backup resolv.conf: %w", err)
|
||||||
|
}
|
||||||
|
content, err := os.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("read %s: %w", filename, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(resolvConf.nameServers) > 1 && resolvConf.nameServers[0] == nameserverIP {
|
||||||
|
newContent := strings.Replace(string(content), fmt.Sprintf("nameserver %s\n", nameserverIP), "", 1)
|
||||||
|
|
||||||
|
stat, err := os.Stat(filename)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("stat %s: %w", filename, err)
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(filename, []byte(newContent), stat.Mode()); err != nil {
|
||||||
|
return fmt.Errorf("write %s: %w", filename, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_parseResolvConf(t *testing.T) {
|
func Test_parseResolvConf(t *testing.T) {
|
||||||
@@ -172,3 +174,131 @@ nameserver 192.168.0.1
|
|||||||
t.Errorf("unexpected resolv.conf content: %v", cfg)
|
t.Errorf("unexpected resolv.conf content: %v", cfg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPrepareOptionsWithTimeout(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
others []string
|
||||||
|
timeout int
|
||||||
|
attempts int
|
||||||
|
expected []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Append new options with timeout and attempts",
|
||||||
|
others: []string{"some config"},
|
||||||
|
timeout: 2,
|
||||||
|
attempts: 2,
|
||||||
|
expected: []string{"some config", "options timeout:2 attempts:2"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Modify existing options to exclude rotate and include timeout and attempts",
|
||||||
|
others: []string{"some config", "options rotate someother"},
|
||||||
|
timeout: 3,
|
||||||
|
attempts: 2,
|
||||||
|
expected: []string{"some config", "options attempts:2 timeout:3 someother"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Existing options with timeout and attempts are updated",
|
||||||
|
others: []string{"some config", "options timeout:4 attempts:3"},
|
||||||
|
timeout: 5,
|
||||||
|
attempts: 4,
|
||||||
|
expected: []string{"some config", "options timeout:5 attempts:4"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Modify existing options, add missing attempts before timeout",
|
||||||
|
others: []string{"some config", "options timeout:4"},
|
||||||
|
timeout: 4,
|
||||||
|
attempts: 3,
|
||||||
|
expected: []string{"some config", "options attempts:3 timeout:4"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tests {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
result := prepareOptionsWithTimeout(tc.others, tc.timeout, tc.attempts)
|
||||||
|
assert.Equal(t, tc.expected, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRemoveFirstNbNameserver(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
content string
|
||||||
|
ipToRemove string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Unrelated nameservers with comments and options",
|
||||||
|
content: `# This is a comment
|
||||||
|
options rotate
|
||||||
|
nameserver 1.1.1.1
|
||||||
|
# Another comment
|
||||||
|
nameserver 8.8.4.4
|
||||||
|
search example.com`,
|
||||||
|
ipToRemove: "9.9.9.9",
|
||||||
|
expected: `# This is a comment
|
||||||
|
options rotate
|
||||||
|
nameserver 1.1.1.1
|
||||||
|
# Another comment
|
||||||
|
nameserver 8.8.4.4
|
||||||
|
search example.com`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "First nameserver matches",
|
||||||
|
content: `search example.com
|
||||||
|
nameserver 9.9.9.9
|
||||||
|
# oof, a comment
|
||||||
|
nameserver 8.8.4.4
|
||||||
|
options attempts:5`,
|
||||||
|
ipToRemove: "9.9.9.9",
|
||||||
|
expected: `search example.com
|
||||||
|
# oof, a comment
|
||||||
|
nameserver 8.8.4.4
|
||||||
|
options attempts:5`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Target IP not the first nameserver",
|
||||||
|
// nolint:dupword
|
||||||
|
content: `# Comment about the first nameserver
|
||||||
|
nameserver 8.8.4.4
|
||||||
|
# Comment before our target
|
||||||
|
nameserver 9.9.9.9
|
||||||
|
options timeout:2`,
|
||||||
|
ipToRemove: "9.9.9.9",
|
||||||
|
// nolint:dupword
|
||||||
|
expected: `# Comment about the first nameserver
|
||||||
|
nameserver 8.8.4.4
|
||||||
|
# Comment before our target
|
||||||
|
nameserver 9.9.9.9
|
||||||
|
options timeout:2`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Only nameserver matches",
|
||||||
|
content: `options debug
|
||||||
|
nameserver 9.9.9.9
|
||||||
|
search localdomain`,
|
||||||
|
ipToRemove: "9.9.9.9",
|
||||||
|
expected: `options debug
|
||||||
|
nameserver 9.9.9.9
|
||||||
|
search localdomain`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
tempDir := t.TempDir()
|
||||||
|
tempFile := filepath.Join(tempDir, "resolv.conf")
|
||||||
|
err := os.WriteFile(tempFile, []byte(tc.content), 0644)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
err = removeFirstNbNameserver(tempFile, tc.ipToRemove)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
content, err := os.ReadFile(tempFile)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, tc.expected, string(content), "The resulting content should match the expected output.")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ func newHostManager(wgInterface string) (hostManager, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("discovered mode is: %s", osManager)
|
log.Infof("System DNS manager discovered: %s", osManager)
|
||||||
return newHostManagerFromType(wgInterface, osManager)
|
return newHostManagerFromType(wgInterface, osManager)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -53,10 +53,12 @@ func (r *resolvconf) applyDNSConfig(config HostDNSConfig) error {
|
|||||||
searchDomainList := searchDomains(config)
|
searchDomainList := searchDomains(config)
|
||||||
searchDomainList = mergeSearchDomains(searchDomainList, r.originalSearchDomains)
|
searchDomainList = mergeSearchDomains(searchDomainList, r.originalSearchDomains)
|
||||||
|
|
||||||
|
options := prepareOptionsWithTimeout(r.othersConfigs, int(dnsFailoverTimeout.Seconds()), dnsFailoverAttempts)
|
||||||
|
|
||||||
buf := prepareResolvConfContent(
|
buf := prepareResolvConfContent(
|
||||||
searchDomainList,
|
searchDomainList,
|
||||||
append([]string{config.ServerIP}, r.originalNameServers...),
|
append([]string{config.ServerIP}, r.originalNameServers...),
|
||||||
r.othersConfigs)
|
options)
|
||||||
|
|
||||||
// create a backup for unclean shutdown detection before the resolv.conf is changed
|
// create a backup for unclean shutdown detection before the resolv.conf is changed
|
||||||
if err := createUncleanShutdownIndicator(defaultResolvConfPath, resolvConfManager, config.ServerIP); err != nil {
|
if err := createUncleanShutdownIndicator(defaultResolvConfPath, resolvConfManager, config.ServerIP); err != nil {
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
gstatus "google.golang.org/grpc/status"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/internal/relay"
|
"github.com/netbirdio/netbird/client/internal/relay"
|
||||||
"github.com/netbirdio/netbird/iface"
|
"github.com/netbirdio/netbird/iface"
|
||||||
)
|
)
|
||||||
@@ -376,6 +379,24 @@ func (d *Status) GetManagementState() ManagementState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsLoginRequired determines if a peer's login has expired.
|
||||||
|
func (d *Status) IsLoginRequired() bool {
|
||||||
|
d.mux.Lock()
|
||||||
|
defer d.mux.Unlock()
|
||||||
|
|
||||||
|
// if peer is connected to the management then login is not expired
|
||||||
|
if d.managementState {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
s, ok := gstatus.FromError(d.managementError)
|
||||||
|
if ok && (s.Code() == codes.InvalidArgument || s.Code() == codes.PermissionDenied) {
|
||||||
|
return true
|
||||||
|
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func (d *Status) GetSignalState() SignalState {
|
func (d *Status) GetSignalState() SignalState {
|
||||||
return SignalState{
|
return SignalState{
|
||||||
d.signalAddress,
|
d.signalAddress,
|
||||||
|
|||||||
82
client/internal/session.go
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/client/internal/peer"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SessionWatcher struct {
|
||||||
|
ctx context.Context
|
||||||
|
mutex sync.Mutex
|
||||||
|
|
||||||
|
peerStatusRecorder *peer.Status
|
||||||
|
watchTicker *time.Ticker
|
||||||
|
|
||||||
|
sendNotification bool
|
||||||
|
onExpireListener func()
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSessionWatcher creates a new instance of SessionWatcher.
|
||||||
|
func NewSessionWatcher(ctx context.Context, peerStatusRecorder *peer.Status) *SessionWatcher {
|
||||||
|
s := &SessionWatcher{
|
||||||
|
ctx: ctx,
|
||||||
|
peerStatusRecorder: peerStatusRecorder,
|
||||||
|
watchTicker: time.NewTicker(2 * time.Second),
|
||||||
|
}
|
||||||
|
go s.startWatcher()
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetOnExpireListener sets the callback func to be called when the session expires.
|
||||||
|
func (s *SessionWatcher) SetOnExpireListener(onExpire func()) {
|
||||||
|
s.mutex.Lock()
|
||||||
|
defer s.mutex.Unlock()
|
||||||
|
s.onExpireListener = onExpire
|
||||||
|
}
|
||||||
|
|
||||||
|
// startWatcher continuously checks if the session requires login and
|
||||||
|
// calls the onExpireListener if login is required.
|
||||||
|
func (s *SessionWatcher) startWatcher() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-s.ctx.Done():
|
||||||
|
s.watchTicker.Stop()
|
||||||
|
return
|
||||||
|
case <-s.watchTicker.C:
|
||||||
|
managementState := s.peerStatusRecorder.GetManagementState()
|
||||||
|
if managementState.Connected {
|
||||||
|
s.sendNotification = true
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoginRequired := s.peerStatusRecorder.IsLoginRequired()
|
||||||
|
if isLoginRequired && s.sendNotification && s.onExpireListener != nil {
|
||||||
|
s.mutex.Lock()
|
||||||
|
s.onExpireListener()
|
||||||
|
s.sendNotification = false
|
||||||
|
s.mutex.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckUIApp checks whether UI application is running.
|
||||||
|
func CheckUIApp() bool {
|
||||||
|
cmd := exec.Command("ps", "-ef")
|
||||||
|
output, err := cmd.Output()
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
lines := strings.Split(string(output), "\n")
|
||||||
|
for _, line := range lines {
|
||||||
|
if strings.Contains(line, "netbird-ui") && !strings.Contains(line, "grep") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
@@ -3,9 +3,15 @@ package server
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/cenkalti/backoff/v4"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/internal/auth"
|
"github.com/netbirdio/netbird/client/internal/auth"
|
||||||
"github.com/netbirdio/netbird/client/system"
|
"github.com/netbirdio/netbird/client/system"
|
||||||
|
|
||||||
@@ -21,7 +27,17 @@ import (
|
|||||||
"github.com/netbirdio/netbird/version"
|
"github.com/netbirdio/netbird/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
const probeThreshold = time.Second * 5
|
const (
|
||||||
|
probeThreshold = time.Second * 5
|
||||||
|
retryInitialIntervalVar = "NB_CONN_RETRY_INTERVAL_TIME"
|
||||||
|
maxRetryIntervalVar = "NB_CONN_MAX_RETRY_INTERVAL_TIME"
|
||||||
|
maxRetryTimeVar = "NB_CONN_MAX_RETRY_TIME_TIME"
|
||||||
|
retryMultiplierVar = "NB_CONN_RETRY_MULTIPLIER"
|
||||||
|
defaultInitialRetryTime = 14 * 24 * time.Hour
|
||||||
|
defaultMaxRetryInterval = 60 * time.Minute
|
||||||
|
defaultMaxRetryTime = 14 * 24 * time.Hour
|
||||||
|
defaultRetryMultiplier = 1.7
|
||||||
|
)
|
||||||
|
|
||||||
// Server for service control.
|
// Server for service control.
|
||||||
type Server struct {
|
type Server struct {
|
||||||
@@ -39,6 +55,7 @@ type Server struct {
|
|||||||
proto.UnimplementedDaemonServiceServer
|
proto.UnimplementedDaemonServiceServer
|
||||||
|
|
||||||
statusRecorder *peer.Status
|
statusRecorder *peer.Status
|
||||||
|
sessionWatcher *internal.SessionWatcher
|
||||||
|
|
||||||
mgmProbe *internal.Probe
|
mgmProbe *internal.Probe
|
||||||
signalProbe *internal.Probe
|
signalProbe *internal.Probe
|
||||||
@@ -116,17 +133,116 @@ func (s *Server) Start() error {
|
|||||||
s.statusRecorder.UpdateManagementAddress(config.ManagementURL.String())
|
s.statusRecorder.UpdateManagementAddress(config.ManagementURL.String())
|
||||||
s.statusRecorder.UpdateRosenpass(config.RosenpassEnabled, config.RosenpassPermissive)
|
s.statusRecorder.UpdateRosenpass(config.RosenpassEnabled, config.RosenpassPermissive)
|
||||||
|
|
||||||
|
if s.sessionWatcher == nil {
|
||||||
|
s.sessionWatcher = internal.NewSessionWatcher(s.rootCtx, s.statusRecorder)
|
||||||
|
s.sessionWatcher.SetOnExpireListener(s.onSessionExpire)
|
||||||
|
}
|
||||||
|
|
||||||
if !config.DisableAutoConnect {
|
if !config.DisableAutoConnect {
|
||||||
go func() {
|
go s.connectWithRetryRuns(ctx, config, s.statusRecorder, s.mgmProbe, s.signalProbe, s.relayProbe, s.wgProbe)
|
||||||
if err := internal.RunClientWithProbes(ctx, config, s.statusRecorder, s.mgmProbe, s.signalProbe, s.relayProbe, s.wgProbe); err != nil {
|
|
||||||
log.Errorf("init connections: %v", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// connectWithRetryRuns runs the client connection with a backoff strategy where we retry the operation as additional
|
||||||
|
// mechanism to keep the client connected even when the connection is lost.
|
||||||
|
// we cancel retry if the client receive a stop or down command, or if disable auto connect is configured.
|
||||||
|
func (s *Server) connectWithRetryRuns(ctx context.Context, config *internal.Config, statusRecorder *peer.Status,
|
||||||
|
mgmProbe *internal.Probe, signalProbe *internal.Probe, relayProbe *internal.Probe, wgProbe *internal.Probe) {
|
||||||
|
backOff := getConnectWithBackoff(ctx)
|
||||||
|
retryStarted := false
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
t := time.NewTicker(24 * time.Hour)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
t.Stop()
|
||||||
|
return
|
||||||
|
case <-t.C:
|
||||||
|
if retryStarted {
|
||||||
|
|
||||||
|
mgmtState := statusRecorder.GetManagementState()
|
||||||
|
signalState := statusRecorder.GetSignalState()
|
||||||
|
if mgmtState.Connected && signalState.Connected {
|
||||||
|
log.Tracef("resetting status")
|
||||||
|
retryStarted = false
|
||||||
|
} else {
|
||||||
|
log.Tracef("not resetting status: mgmt: %v, signal: %v", mgmtState.Connected, signalState.Connected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
runOperation := func() error {
|
||||||
|
log.Tracef("running client connection")
|
||||||
|
err := internal.RunClientWithProbes(ctx, config, statusRecorder, mgmProbe, signalProbe, relayProbe, wgProbe)
|
||||||
|
if err != nil {
|
||||||
|
log.Debugf("run client connection exited with error: %v. Will retry in the background", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.DisableAutoConnect {
|
||||||
|
return backoff.Permanent(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !retryStarted {
|
||||||
|
retryStarted = true
|
||||||
|
backOff.Reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Tracef("client connection exited")
|
||||||
|
return fmt.Errorf("client connection exited")
|
||||||
|
}
|
||||||
|
|
||||||
|
err := backoff.Retry(runOperation, backOff)
|
||||||
|
if s, ok := gstatus.FromError(err); ok && s.Code() != codes.Canceled {
|
||||||
|
log.Errorf("received an error when trying to connect: %v", err)
|
||||||
|
} else {
|
||||||
|
log.Tracef("retry canceled")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getConnectWithBackoff returns a backoff with exponential backoff strategy for connection retries
|
||||||
|
func getConnectWithBackoff(ctx context.Context) backoff.BackOff {
|
||||||
|
initialInterval := parseEnvDuration(retryInitialIntervalVar, defaultInitialRetryTime)
|
||||||
|
maxInterval := parseEnvDuration(maxRetryIntervalVar, defaultMaxRetryInterval)
|
||||||
|
maxElapsedTime := parseEnvDuration(maxRetryTimeVar, defaultMaxRetryTime)
|
||||||
|
multiplier := defaultRetryMultiplier
|
||||||
|
|
||||||
|
if envValue := os.Getenv(retryMultiplierVar); envValue != "" {
|
||||||
|
// parse the multiplier from the environment variable string value to float64
|
||||||
|
value, err := strconv.ParseFloat(envValue, 64)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("unable to parse environment variable %s: %s. using default: %f", retryMultiplierVar, envValue, multiplier)
|
||||||
|
} else {
|
||||||
|
multiplier = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return backoff.WithContext(&backoff.ExponentialBackOff{
|
||||||
|
InitialInterval: initialInterval,
|
||||||
|
RandomizationFactor: 1,
|
||||||
|
Multiplier: multiplier,
|
||||||
|
MaxInterval: maxInterval,
|
||||||
|
MaxElapsedTime: maxElapsedTime, // 14 days
|
||||||
|
Stop: backoff.Stop,
|
||||||
|
Clock: backoff.SystemClock,
|
||||||
|
}, ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseEnvDuration parses the environment variable and returns the duration
|
||||||
|
func parseEnvDuration(envVar string, defaultDuration time.Duration) time.Duration {
|
||||||
|
if envValue := os.Getenv(envVar); envValue != "" {
|
||||||
|
if duration, err := time.ParseDuration(envValue); err == nil {
|
||||||
|
return duration
|
||||||
|
}
|
||||||
|
log.Warnf("unable to parse environment variable %s: %s. using default: %s", envVar, envValue, defaultDuration)
|
||||||
|
}
|
||||||
|
return defaultDuration
|
||||||
|
}
|
||||||
|
|
||||||
// loginAttempt attempts to login using the provided information. it returns a status in case something fails
|
// loginAttempt attempts to login using the provided information. it returns a status in case something fails
|
||||||
func (s *Server) loginAttempt(ctx context.Context, setupKey, jwtToken string) (internal.StatusType, error) {
|
func (s *Server) loginAttempt(ctx context.Context, setupKey, jwtToken string) (internal.StatusType, error) {
|
||||||
var status internal.StatusType
|
var status internal.StatusType
|
||||||
@@ -437,12 +553,7 @@ func (s *Server) Up(callerCtx context.Context, _ *proto.UpRequest) (*proto.UpRes
|
|||||||
s.statusRecorder.UpdateManagementAddress(s.config.ManagementURL.String())
|
s.statusRecorder.UpdateManagementAddress(s.config.ManagementURL.String())
|
||||||
s.statusRecorder.UpdateRosenpass(s.config.RosenpassEnabled, s.config.RosenpassPermissive)
|
s.statusRecorder.UpdateRosenpass(s.config.RosenpassEnabled, s.config.RosenpassPermissive)
|
||||||
|
|
||||||
go func() {
|
go s.connectWithRetryRuns(ctx, s.config, s.statusRecorder, s.mgmProbe, s.signalProbe, s.relayProbe, s.wgProbe)
|
||||||
if err := internal.RunClientWithProbes(ctx, s.config, s.statusRecorder, s.mgmProbe, s.signalProbe, s.relayProbe, s.wgProbe); err != nil {
|
|
||||||
log.Errorf("run client connection: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
return &proto.UpResponse{}, nil
|
return &proto.UpResponse{}, nil
|
||||||
}
|
}
|
||||||
@@ -542,6 +653,17 @@ func (s *Server) GetConfig(_ context.Context, _ *proto.GetConfigRequest) (*proto
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Server) onSessionExpire() {
|
||||||
|
if runtime.GOOS != "windows" {
|
||||||
|
isUIActive := internal.CheckUIApp()
|
||||||
|
if !isUIActive {
|
||||||
|
if err := sendTerminalNotification(); err != nil {
|
||||||
|
log.Errorf("send session expire terminal notification: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func toProtoFullStatus(fullStatus peer.FullStatus) *proto.FullStatus {
|
func toProtoFullStatus(fullStatus peer.FullStatus) *proto.FullStatus {
|
||||||
pbFullStatus := proto.FullStatus{
|
pbFullStatus := proto.FullStatus{
|
||||||
ManagementState: &proto.ManagementState{},
|
ManagementState: &proto.ManagementState{},
|
||||||
@@ -604,3 +726,31 @@ func toProtoFullStatus(fullStatus peer.FullStatus) *proto.FullStatus {
|
|||||||
|
|
||||||
return &pbFullStatus
|
return &pbFullStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// sendTerminalNotification sends a terminal notification message
|
||||||
|
// to inform the user that the NetBird connection session has expired.
|
||||||
|
func sendTerminalNotification() error {
|
||||||
|
message := "NetBird connection session expired\n\nPlease re-authenticate to connect to the network."
|
||||||
|
echoCmd := exec.Command("echo", message)
|
||||||
|
wallCmd := exec.Command("sudo", "wall")
|
||||||
|
|
||||||
|
echoCmdStdout, err := echoCmd.StdoutPipe()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
wallCmd.Stdin = echoCmdStdout
|
||||||
|
|
||||||
|
if err := echoCmd.Start(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := wallCmd.Start(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := echoCmd.Wait(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return wallCmd.Wait()
|
||||||
|
}
|
||||||
|
|||||||
157
client/server/server_test.go
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/keepalive"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/client/internal"
|
||||||
|
"github.com/netbirdio/netbird/client/internal/peer"
|
||||||
|
mgmtProto "github.com/netbirdio/netbird/management/proto"
|
||||||
|
"github.com/netbirdio/netbird/management/server"
|
||||||
|
"github.com/netbirdio/netbird/management/server/activity"
|
||||||
|
"github.com/netbirdio/netbird/signal/proto"
|
||||||
|
signalServer "github.com/netbirdio/netbird/signal/server"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
kaep = keepalive.EnforcementPolicy{
|
||||||
|
MinTime: 15 * time.Second,
|
||||||
|
PermitWithoutStream: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
kasp = keepalive.ServerParameters{
|
||||||
|
MaxConnectionIdle: 15 * time.Second,
|
||||||
|
MaxConnectionAgeGrace: 5 * time.Second,
|
||||||
|
Time: 5 * time.Second,
|
||||||
|
Timeout: 2 * time.Second,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestConnectWithRetryRuns checks that the connectWithRetry function runs and runs the retries according to the times specified via environment variables
|
||||||
|
// we will use a management server started via to simulate the server and capture the number of retries
|
||||||
|
func TestConnectWithRetryRuns(t *testing.T) {
|
||||||
|
// start the signal server
|
||||||
|
_, signalAddr, err := startSignal()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to start signal server: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
counter := 0
|
||||||
|
// start the management server
|
||||||
|
_, mgmtAddr, err := startManagement(t, signalAddr, &counter)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to start management server: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := internal.CtxInitState(context.Background())
|
||||||
|
|
||||||
|
ctx, cancel := context.WithDeadline(ctx, time.Now().Add(30*time.Second))
|
||||||
|
defer cancel()
|
||||||
|
// create new server
|
||||||
|
s := New(ctx, t.TempDir()+"/config.json", "debug")
|
||||||
|
s.latestConfigInput.ManagementURL = "http://" + mgmtAddr
|
||||||
|
config, err := internal.UpdateOrCreateConfig(s.latestConfigInput)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create config: %v", err)
|
||||||
|
}
|
||||||
|
s.config = config
|
||||||
|
|
||||||
|
s.statusRecorder = peer.NewRecorder(config.ManagementURL.String())
|
||||||
|
t.Setenv(retryInitialIntervalVar, "1s")
|
||||||
|
t.Setenv(maxRetryIntervalVar, "2s")
|
||||||
|
t.Setenv(maxRetryTimeVar, "5s")
|
||||||
|
t.Setenv(retryMultiplierVar, "1")
|
||||||
|
|
||||||
|
s.connectWithRetryRuns(ctx, config, s.statusRecorder, s.mgmProbe, s.signalProbe, s.relayProbe, s.wgProbe)
|
||||||
|
if counter < 3 {
|
||||||
|
t.Fatalf("expected counter > 2, got %d", counter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockServer struct {
|
||||||
|
mgmtProto.ManagementServiceServer
|
||||||
|
counter *int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockServer) Login(ctx context.Context, req *mgmtProto.EncryptedMessage) (*mgmtProto.EncryptedMessage, error) {
|
||||||
|
*m.counter++
|
||||||
|
return m.ManagementServiceServer.Login(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func startManagement(t *testing.T, signalAddr string, counter *int) (*grpc.Server, string, error) {
|
||||||
|
t.Helper()
|
||||||
|
dataDir := t.TempDir()
|
||||||
|
|
||||||
|
config := &server.Config{
|
||||||
|
Stuns: []*server.Host{},
|
||||||
|
TURNConfig: &server.TURNConfig{},
|
||||||
|
Signal: &server.Host{
|
||||||
|
Proto: "http",
|
||||||
|
URI: signalAddr,
|
||||||
|
},
|
||||||
|
Datadir: dataDir,
|
||||||
|
HttpConfig: nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
lis, err := net.Listen("tcp", "localhost:0")
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
s := grpc.NewServer(grpc.KeepaliveEnforcementPolicy(kaep), grpc.KeepaliveParams(kasp))
|
||||||
|
store, err := server.NewStoreFromJson(config.Datadir, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
peersUpdateManager := server.NewPeersUpdateManager(nil)
|
||||||
|
eventStore := &activity.InMemoryEventStore{}
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
accountManager, err := server.BuildManager(store, peersUpdateManager, nil, "", "", eventStore, nil, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
turnManager := server.NewTimeBasedAuthSecretsManager(peersUpdateManager, config.TURNConfig)
|
||||||
|
mgmtServer, err := server.NewServer(config, accountManager, peersUpdateManager, turnManager, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
mock := &mockServer{
|
||||||
|
ManagementServiceServer: mgmtServer,
|
||||||
|
counter: counter,
|
||||||
|
}
|
||||||
|
mgmtProto.RegisterManagementServiceServer(s, mock)
|
||||||
|
go func() {
|
||||||
|
if err = s.Serve(lis); err != nil {
|
||||||
|
log.Fatalf("failed to serve: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return s, lis.Addr().String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func startSignal() (*grpc.Server, string, error) {
|
||||||
|
s := grpc.NewServer(grpc.KeepaliveEnforcementPolicy(kaep), grpc.KeepaliveParams(kasp))
|
||||||
|
|
||||||
|
lis, err := net.Listen("tcp", "localhost:0")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("failed to listen: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
proto.RegisterSignalExchangeServer(s, signalServer.NewServer())
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
if err = s.Serve(lis); err != nil {
|
||||||
|
log.Fatalf("failed to serve: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return s, lis.Addr().String(), nil
|
||||||
|
}
|
||||||
@@ -165,6 +165,10 @@ func sysProductName() (string, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
// `ComputerSystemProduct` could be empty on some virtualized systems
|
||||||
|
if len(dst) < 1 {
|
||||||
|
return "unknown", nil
|
||||||
|
}
|
||||||
return dst[0].Name, nil
|
return dst[0].Name, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ func main() {
|
|||||||
|
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
a := app.New()
|
a := app.NewWithID("NetBird")
|
||||||
a.SetIcon(fyne.NewStaticResource("netbird", iconDisconnectedPNG))
|
a.SetIcon(fyne.NewStaticResource("netbird", iconDisconnectedPNG))
|
||||||
|
|
||||||
client := newServiceClient(daemonAddr, a, showSettings)
|
client := newServiceClient(daemonAddr, a, showSettings)
|
||||||
@@ -82,17 +82,23 @@ var iconConnectedICO []byte
|
|||||||
//go:embed netbird-systemtray-connected.png
|
//go:embed netbird-systemtray-connected.png
|
||||||
var iconConnectedPNG []byte
|
var iconConnectedPNG []byte
|
||||||
|
|
||||||
//go:embed netbird-systemtray-default.ico
|
//go:embed netbird-systemtray-disconnected.ico
|
||||||
var iconDisconnectedICO []byte
|
var iconDisconnectedICO []byte
|
||||||
|
|
||||||
//go:embed netbird-systemtray-default.png
|
//go:embed netbird-systemtray-disconnected.png
|
||||||
var iconDisconnectedPNG []byte
|
var iconDisconnectedPNG []byte
|
||||||
|
|
||||||
//go:embed netbird-systemtray-update.ico
|
//go:embed netbird-systemtray-update-disconnected.ico
|
||||||
var iconUpdateICO []byte
|
var iconUpdateDisconnectedICO []byte
|
||||||
|
|
||||||
//go:embed netbird-systemtray-update.png
|
//go:embed netbird-systemtray-update-disconnected.png
|
||||||
var iconUpdatePNG []byte
|
var iconUpdateDisconnectedPNG []byte
|
||||||
|
|
||||||
|
//go:embed netbird-systemtray-update-connected.ico
|
||||||
|
var iconUpdateConnectedICO []byte
|
||||||
|
|
||||||
|
//go:embed netbird-systemtray-update-connected.png
|
||||||
|
var iconUpdateConnectedPNG []byte
|
||||||
|
|
||||||
//go:embed netbird-systemtray-update-cloud.ico
|
//go:embed netbird-systemtray-update-cloud.ico
|
||||||
var iconUpdateCloudICO []byte
|
var iconUpdateCloudICO []byte
|
||||||
@@ -105,10 +111,11 @@ type serviceClient struct {
|
|||||||
addr string
|
addr string
|
||||||
conn proto.DaemonServiceClient
|
conn proto.DaemonServiceClient
|
||||||
|
|
||||||
icConnected []byte
|
icConnected []byte
|
||||||
icDisconnected []byte
|
icDisconnected []byte
|
||||||
icUpdate []byte
|
icUpdateConnected []byte
|
||||||
icUpdateCloud []byte
|
icUpdateDisconnected []byte
|
||||||
|
icUpdateCloud []byte
|
||||||
|
|
||||||
// systray menu items
|
// systray menu items
|
||||||
mStatus *systray.MenuItem
|
mStatus *systray.MenuItem
|
||||||
@@ -123,9 +130,10 @@ type serviceClient struct {
|
|||||||
mQuit *systray.MenuItem
|
mQuit *systray.MenuItem
|
||||||
|
|
||||||
// application with main windows.
|
// application with main windows.
|
||||||
app fyne.App
|
app fyne.App
|
||||||
wSettings fyne.Window
|
wSettings fyne.Window
|
||||||
showSettings bool
|
showSettings bool
|
||||||
|
sendNotification bool
|
||||||
|
|
||||||
// input elements for settings form
|
// input elements for settings form
|
||||||
iMngURL *widget.Entry
|
iMngURL *widget.Entry
|
||||||
@@ -139,6 +147,7 @@ type serviceClient struct {
|
|||||||
preSharedKey string
|
preSharedKey string
|
||||||
adminURL string
|
adminURL string
|
||||||
|
|
||||||
|
connected bool
|
||||||
update *version.Update
|
update *version.Update
|
||||||
daemonVersion string
|
daemonVersion string
|
||||||
updateIndicationLock sync.Mutex
|
updateIndicationLock sync.Mutex
|
||||||
@@ -150,9 +159,10 @@ type serviceClient struct {
|
|||||||
// This constructor also builds the UI elements for the 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(),
|
||||||
addr: addr,
|
addr: addr,
|
||||||
app: a,
|
app: a,
|
||||||
|
sendNotification: false,
|
||||||
|
|
||||||
showSettings: showSettings,
|
showSettings: showSettings,
|
||||||
update: version.NewUpdate(),
|
update: version.NewUpdate(),
|
||||||
@@ -161,13 +171,15 @@ func newServiceClient(addr string, a fyne.App, showSettings bool) *serviceClient
|
|||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
s.icConnected = iconConnectedICO
|
s.icConnected = iconConnectedICO
|
||||||
s.icDisconnected = iconDisconnectedICO
|
s.icDisconnected = iconDisconnectedICO
|
||||||
s.icUpdate = iconUpdateICO
|
s.icUpdateConnected = iconUpdateConnectedICO
|
||||||
|
s.icUpdateDisconnected = iconUpdateDisconnectedICO
|
||||||
s.icUpdateCloud = iconUpdateCloudICO
|
s.icUpdateCloud = iconUpdateCloudICO
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
s.icConnected = iconConnectedPNG
|
s.icConnected = iconConnectedPNG
|
||||||
s.icDisconnected = iconDisconnectedPNG
|
s.icDisconnected = iconDisconnectedPNG
|
||||||
s.icUpdate = iconUpdatePNG
|
s.icUpdateConnected = iconUpdateConnectedPNG
|
||||||
|
s.icUpdateDisconnected = iconUpdateDisconnectedPNG
|
||||||
s.icUpdateCloud = iconUpdateCloudPNG
|
s.icUpdateCloud = iconUpdateCloudPNG
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -367,9 +379,18 @@ func (s *serviceClient) updateStatus() error {
|
|||||||
s.updateIndicationLock.Lock()
|
s.updateIndicationLock.Lock()
|
||||||
defer s.updateIndicationLock.Unlock()
|
defer s.updateIndicationLock.Unlock()
|
||||||
|
|
||||||
|
// notify the user when the session has expired
|
||||||
|
if status.Status == string(internal.StatusNeedsLogin) {
|
||||||
|
s.onSessionExpire()
|
||||||
|
}
|
||||||
|
|
||||||
var systrayIconState bool
|
var systrayIconState bool
|
||||||
if status.Status == string(internal.StatusConnected) && !s.mUp.Disabled() {
|
if status.Status == string(internal.StatusConnected) && !s.mUp.Disabled() {
|
||||||
if !s.isUpdateIconActive {
|
s.connected = true
|
||||||
|
s.sendNotification = true
|
||||||
|
if s.isUpdateIconActive {
|
||||||
|
systray.SetIcon(s.icUpdateConnected)
|
||||||
|
} else {
|
||||||
systray.SetIcon(s.icConnected)
|
systray.SetIcon(s.icConnected)
|
||||||
}
|
}
|
||||||
systray.SetTooltip("NetBird (Connected)")
|
systray.SetTooltip("NetBird (Connected)")
|
||||||
@@ -378,7 +399,10 @@ func (s *serviceClient) updateStatus() error {
|
|||||||
s.mDown.Enable()
|
s.mDown.Enable()
|
||||||
systrayIconState = true
|
systrayIconState = true
|
||||||
} else if status.Status != string(internal.StatusConnected) && s.mUp.Disabled() {
|
} else if status.Status != string(internal.StatusConnected) && s.mUp.Disabled() {
|
||||||
if !s.isUpdateIconActive {
|
s.connected = false
|
||||||
|
if s.isUpdateIconActive {
|
||||||
|
systray.SetIcon(s.icUpdateDisconnected)
|
||||||
|
} else {
|
||||||
systray.SetIcon(s.icDisconnected)
|
systray.SetIcon(s.icDisconnected)
|
||||||
}
|
}
|
||||||
systray.SetTooltip("NetBird (Disconnected)")
|
systray.SetTooltip("NetBird (Disconnected)")
|
||||||
@@ -605,10 +629,30 @@ func (s *serviceClient) onUpdateAvailable() {
|
|||||||
defer s.updateIndicationLock.Unlock()
|
defer s.updateIndicationLock.Unlock()
|
||||||
|
|
||||||
s.mUpdate.Show()
|
s.mUpdate.Show()
|
||||||
s.mAbout.SetIcon(s.icUpdateCloud)
|
|
||||||
|
|
||||||
s.isUpdateIconActive = true
|
s.isUpdateIconActive = true
|
||||||
systray.SetIcon(s.icUpdate)
|
|
||||||
|
if s.connected {
|
||||||
|
systray.SetIcon(s.icUpdateConnected)
|
||||||
|
} else {
|
||||||
|
systray.SetIcon(s.icUpdateDisconnected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// onSessionExpire sends a notification to the user when the session expires.
|
||||||
|
func (s *serviceClient) onSessionExpire() {
|
||||||
|
if s.sendNotification {
|
||||||
|
title := "Connection session expired"
|
||||||
|
if runtime.GOOS == "darwin" {
|
||||||
|
title = "NetBird connection session expired"
|
||||||
|
}
|
||||||
|
s.app.SendNotification(
|
||||||
|
fyne.NewNotification(
|
||||||
|
title,
|
||||||
|
"Please re-authenticate to connect to the network",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
s.sendNotification = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func openURL(url string) error {
|
func openURL(url string) error {
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 8.9 KiB |
|
Before Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 4.8 KiB |
BIN
client/ui/netbird-systemtray-disconnected.ico
Normal file
|
After Width: | Height: | Size: 5.0 KiB |
BIN
client/ui/netbird-systemtray-disconnected.png
Normal file
|
After Width: | Height: | Size: 9.0 KiB |
BIN
client/ui/netbird-systemtray-update-connected.ico
Normal file
|
After Width: | Height: | Size: 7.5 KiB |
BIN
client/ui/netbird-systemtray-update-connected.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
client/ui/netbird-systemtray-update-disconnected.ico
Normal file
|
After Width: | Height: | Size: 7.8 KiB |
BIN
client/ui/netbird-systemtray-update-disconnected.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 7.3 KiB |
@@ -8,7 +8,7 @@ import (
|
|||||||
"golang.zx2c4.com/wireguard/tun/netstack"
|
"golang.zx2c4.com/wireguard/tun/netstack"
|
||||||
)
|
)
|
||||||
|
|
||||||
type NetStackTun struct {
|
type NetStackTun struct { //nolint:revive
|
||||||
address string
|
address string
|
||||||
mtu int
|
mtu int
|
||||||
listenAddress string
|
listenAddress string
|
||||||
|
|||||||
@@ -43,21 +43,18 @@ download_geolite_mmdb() {
|
|||||||
mkdir -p "$EXTRACTION_DIR"
|
mkdir -p "$EXTRACTION_DIR"
|
||||||
tar -xzvf "$DATABASE_FILE" > /dev/null 2>&1
|
tar -xzvf "$DATABASE_FILE" > /dev/null 2>&1
|
||||||
|
|
||||||
# Create a SHA256 signature file
|
|
||||||
MMDB_FILE="GeoLite2-City.mmdb"
|
MMDB_FILE="GeoLite2-City.mmdb"
|
||||||
cd "$EXTRACTION_DIR"
|
cp "$EXTRACTION_DIR"/"$MMDB_FILE" $MMDB_FILE
|
||||||
sha256sum "$MMDB_FILE" > "$MMDB_FILE.sha256"
|
|
||||||
echo "SHA256 signature created for $MMDB_FILE."
|
|
||||||
cd - > /dev/null 2>&1
|
|
||||||
|
|
||||||
# Remove downloaded files
|
# Remove downloaded files
|
||||||
|
rm -r "$EXTRACTION_DIR"
|
||||||
rm "$DATABASE_FILE" "$SIGNATURE_FILE"
|
rm "$DATABASE_FILE" "$SIGNATURE_FILE"
|
||||||
|
|
||||||
# Done. Print next steps
|
# Done. Print next steps
|
||||||
echo ""
|
echo ""
|
||||||
echo "Process completed successfully."
|
echo "Process completed successfully."
|
||||||
echo "Now you can place $EXTRACTION_DIR/$MMDB_FILE to 'datadir' of management service."
|
echo "Now you can place $MMDB_FILE to 'datadir' of management service."
|
||||||
echo -e "Example:\n\tdocker compose cp $EXTRACTION_DIR/$MMDB_FILE management:/var/lib/netbird/"
|
echo -e "Example:\n\tdocker compose cp $MMDB_FILE management:/var/lib/netbird/"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -137,6 +137,13 @@ create_new_application() {
|
|||||||
BASE_REDIRECT_URL2=$5
|
BASE_REDIRECT_URL2=$5
|
||||||
LOGOUT_URL=$6
|
LOGOUT_URL=$6
|
||||||
ZITADEL_DEV_MODE=$7
|
ZITADEL_DEV_MODE=$7
|
||||||
|
DEVICE_CODE=$8
|
||||||
|
|
||||||
|
if [[ $DEVICE_CODE == "true" ]]; then
|
||||||
|
GRANT_TYPES='["OIDC_GRANT_TYPE_AUTHORIZATION_CODE","OIDC_GRANT_TYPE_DEVICE_CODE","OIDC_GRANT_TYPE_REFRESH_TOKEN"]'
|
||||||
|
else
|
||||||
|
GRANT_TYPES='["OIDC_GRANT_TYPE_AUTHORIZATION_CODE","OIDC_GRANT_TYPE_REFRESH_TOKEN"]'
|
||||||
|
fi
|
||||||
|
|
||||||
RESPONSE=$(
|
RESPONSE=$(
|
||||||
curl -sS -X POST "$INSTANCE_URL/management/v1/projects/$PROJECT_ID/apps/oidc" \
|
curl -sS -X POST "$INSTANCE_URL/management/v1/projects/$PROJECT_ID/apps/oidc" \
|
||||||
@@ -154,10 +161,7 @@ create_new_application() {
|
|||||||
"RESPONSETypes": [
|
"RESPONSETypes": [
|
||||||
"OIDC_RESPONSE_TYPE_CODE"
|
"OIDC_RESPONSE_TYPE_CODE"
|
||||||
],
|
],
|
||||||
"grantTypes": [
|
"grantTypes": '"$GRANT_TYPES"',
|
||||||
"OIDC_GRANT_TYPE_AUTHORIZATION_CODE",
|
|
||||||
"OIDC_GRANT_TYPE_REFRESH_TOKEN"
|
|
||||||
],
|
|
||||||
"appType": "OIDC_APP_TYPE_USER_AGENT",
|
"appType": "OIDC_APP_TYPE_USER_AGENT",
|
||||||
"authMethodType": "OIDC_AUTH_METHOD_TYPE_NONE",
|
"authMethodType": "OIDC_AUTH_METHOD_TYPE_NONE",
|
||||||
"version": "OIDC_VERSION_1_0",
|
"version": "OIDC_VERSION_1_0",
|
||||||
@@ -340,10 +344,10 @@ init_zitadel() {
|
|||||||
|
|
||||||
# create zitadel spa applications
|
# create zitadel spa applications
|
||||||
echo "Creating new Zitadel SPA Dashboard application"
|
echo "Creating new Zitadel SPA Dashboard application"
|
||||||
DASHBOARD_APPLICATION_CLIENT_ID=$(create_new_application "$INSTANCE_URL" "$PAT" "Dashboard" "$BASE_REDIRECT_URL/nb-auth" "$BASE_REDIRECT_URL/nb-silent-auth" "$BASE_REDIRECT_URL/" "$ZITADEL_DEV_MODE")
|
DASHBOARD_APPLICATION_CLIENT_ID=$(create_new_application "$INSTANCE_URL" "$PAT" "Dashboard" "$BASE_REDIRECT_URL/nb-auth" "$BASE_REDIRECT_URL/nb-silent-auth" "$BASE_REDIRECT_URL/" "$ZITADEL_DEV_MODE" "false")
|
||||||
|
|
||||||
echo "Creating new Zitadel SPA Cli application"
|
echo "Creating new Zitadel SPA Cli application"
|
||||||
CLI_APPLICATION_CLIENT_ID=$(create_new_application "$INSTANCE_URL" "$PAT" "Cli" "http://localhost:53000/" "http://localhost:54000/" "http://localhost:53000/" "true")
|
CLI_APPLICATION_CLIENT_ID=$(create_new_application "$INSTANCE_URL" "$PAT" "Cli" "http://localhost:53000/" "http://localhost:54000/" "http://localhost:53000/" "true" "true")
|
||||||
|
|
||||||
MACHINE_USER_ID=$(create_service_user "$INSTANCE_URL" "$PAT")
|
MACHINE_USER_ID=$(create_service_user "$INSTANCE_URL" "$PAT")
|
||||||
|
|
||||||
@@ -561,6 +565,8 @@ renderCaddyfile() {
|
|||||||
reverse_proxy /.well-known/openid-configuration h2c://zitadel:8080
|
reverse_proxy /.well-known/openid-configuration h2c://zitadel:8080
|
||||||
reverse_proxy /openapi/* h2c://zitadel:8080
|
reverse_proxy /openapi/* h2c://zitadel:8080
|
||||||
reverse_proxy /debug/* h2c://zitadel:8080
|
reverse_proxy /debug/* h2c://zitadel:8080
|
||||||
|
reverse_proxy /device/* h2c://zitadel:8080
|
||||||
|
reverse_proxy /device h2c://zitadel:8080
|
||||||
# Dashboard
|
# Dashboard
|
||||||
reverse_proxy /* dashboard:80
|
reverse_proxy /* dashboard:80
|
||||||
}
|
}
|
||||||
@@ -629,6 +635,14 @@ renderManagementJson() {
|
|||||||
"ManagementEndpoint": "$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN/management/v1"
|
"ManagementEndpoint": "$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN/management/v1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"DeviceAuthorizationFlow": {
|
||||||
|
"Provider": "hosted",
|
||||||
|
"ProviderConfig": {
|
||||||
|
"Audience": "$NETBIRD_AUTH_CLIENT_ID_CLI",
|
||||||
|
"ClientID": "$NETBIRD_AUTH_CLIENT_ID_CLI",
|
||||||
|
"Scope": "openid"
|
||||||
|
}
|
||||||
|
},
|
||||||
"PKCEAuthorizationFlow": {
|
"PKCEAuthorizationFlow": {
|
||||||
"ProviderConfig": {
|
"ProviderConfig": {
|
||||||
"Audience": "$NETBIRD_AUTH_CLIENT_ID_CLI",
|
"Audience": "$NETBIRD_AUTH_CLIENT_ID_CLI",
|
||||||
|
|||||||
@@ -26,6 +26,13 @@
|
|||||||
"Username": "",
|
"Username": "",
|
||||||
"Password": null
|
"Password": null
|
||||||
},
|
},
|
||||||
|
"ReverseProxy": {
|
||||||
|
"TrustedHTTPProxies": [],
|
||||||
|
"TrustedHTTPProxiesCount": 0,
|
||||||
|
"TrustedPeers": [
|
||||||
|
"0.0.0.0/0"
|
||||||
|
]
|
||||||
|
},
|
||||||
"Datadir": "",
|
"Datadir": "",
|
||||||
"DataStoreEncryptionKey": "$NETBIRD_DATASTORE_ENC_KEY",
|
"DataStoreEncryptionKey": "$NETBIRD_DATASTORE_ENC_KEY",
|
||||||
"StoreConfig": {
|
"StoreConfig": {
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ server {
|
|||||||
proxy_set_header X-Scheme $scheme;
|
proxy_set_header X-Scheme $scheme;
|
||||||
proxy_set_header X-Forwarded-Proto https;
|
proxy_set_header X-Forwarded-Proto https;
|
||||||
proxy_set_header X-Forwarded-Host $host;
|
proxy_set_header X-Forwarded-Host $host;
|
||||||
|
grpc_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
|
||||||
# Proxy dashboard
|
# Proxy dashboard
|
||||||
location / {
|
location / {
|
||||||
|
|||||||
@@ -363,10 +363,11 @@ func Test_SystemMetaDataFromClient(t *testing.T) {
|
|||||||
WiretrusteeVersion: info.WiretrusteeVersion,
|
WiretrusteeVersion: info.WiretrusteeVersion,
|
||||||
KernelVersion: info.KernelVersion,
|
KernelVersion: info.KernelVersion,
|
||||||
|
|
||||||
NetworkAddresses: protoNetAddr,
|
NetworkAddresses: protoNetAddr,
|
||||||
SysSerialNumber: info.SystemSerialNumber,
|
SysSerialNumber: info.SystemSerialNumber,
|
||||||
SysProductName: info.SystemProductName,
|
SysProductName: info.SystemProductName,
|
||||||
SysManufacturer: info.SystemManufacturer,
|
SysManufacturer: info.SystemManufacturer,
|
||||||
|
Environment: &mgmtProto.Environment{Cloud: info.Environment.Cloud, Platform: info.Environment.Platform},
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.Equal(t, ValidKey, actualValidKey)
|
assert.Equal(t, ValidKey, actualValidKey)
|
||||||
@@ -407,7 +408,9 @@ func isEqual(a, b *mgmtProto.PeerSystemMeta) bool {
|
|||||||
a.GetUiVersion() == b.GetUiVersion() &&
|
a.GetUiVersion() == b.GetUiVersion() &&
|
||||||
a.GetSysSerialNumber() == b.GetSysSerialNumber() &&
|
a.GetSysSerialNumber() == b.GetSysSerialNumber() &&
|
||||||
a.GetSysProductName() == b.GetSysProductName() &&
|
a.GetSysProductName() == b.GetSysProductName() &&
|
||||||
a.GetSysManufacturer() == b.GetSysManufacturer()
|
a.GetSysManufacturer() == b.GetSysManufacturer() &&
|
||||||
|
a.GetEnvironment().Cloud == b.GetEnvironment().Cloud &&
|
||||||
|
a.GetEnvironment().Platform == b.GetEnvironment().Platform
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_GetDeviceAuthorizationFlow(t *testing.T) {
|
func Test_GetDeviceAuthorizationFlow(t *testing.T) {
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ import (
|
|||||||
"github.com/netbirdio/netbird/management/proto"
|
"github.com/netbirdio/netbird/management/proto"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const ConnectTimeout = 10 * time.Second
|
||||||
|
|
||||||
// ConnStateNotifier is a wrapper interface of the status recorders
|
// ConnStateNotifier is a wrapper interface of the status recorders
|
||||||
type ConnStateNotifier interface {
|
type ConnStateNotifier interface {
|
||||||
MarkManagementDisconnected(error)
|
MarkManagementDisconnected(error)
|
||||||
@@ -49,7 +51,7 @@ func NewClient(ctx context.Context, addr string, ourPrivateKey wgtypes.Key, tlsE
|
|||||||
transportOption = grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{}))
|
transportOption = grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{}))
|
||||||
}
|
}
|
||||||
|
|
||||||
mgmCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
mgmCtx, cancel := context.WithTimeout(ctx, ConnectTimeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
conn, err := grpc.DialContext(
|
conn, err := grpc.DialContext(
|
||||||
mgmCtx,
|
mgmCtx,
|
||||||
@@ -318,7 +320,7 @@ func (c *GrpcClient) login(serverKey wgtypes.Key, req *proto.LoginRequest) (*pro
|
|||||||
log.Errorf("failed to encrypt message: %s", err)
|
log.Errorf("failed to encrypt message: %s", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
mgmCtx, cancel := context.WithTimeout(c.ctx, 5*time.Second)
|
mgmCtx, cancel := context.WithTimeout(c.ctx, ConnectTimeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
resp, err := c.realClient.Login(mgmCtx, &proto.EncryptedMessage{
|
resp, err := c.realClient.Login(mgmCtx, &proto.EncryptedMessage{
|
||||||
WgPubKey: c.key.PublicKey().String(),
|
WgPubKey: c.key.PublicKey().String(),
|
||||||
@@ -474,5 +476,9 @@ func infoToMetaData(info *system.Info) *proto.PeerSystemMeta {
|
|||||||
SysSerialNumber: info.SystemSerialNumber,
|
SysSerialNumber: info.SystemSerialNumber,
|
||||||
SysManufacturer: info.SystemManufacturer,
|
SysManufacturer: info.SystemManufacturer,
|
||||||
SysProductName: info.SystemProductName,
|
SysProductName: info.SystemProductName,
|
||||||
|
Environment: &proto.Environment{
|
||||||
|
Cloud: info.Environment.Cloud,
|
||||||
|
Platform: info.Environment.Platform,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ import (
|
|||||||
"github.com/netbirdio/netbird/management/server/metrics"
|
"github.com/netbirdio/netbird/management/server/metrics"
|
||||||
"github.com/netbirdio/netbird/management/server/telemetry"
|
"github.com/netbirdio/netbird/management/server/telemetry"
|
||||||
"github.com/netbirdio/netbird/util"
|
"github.com/netbirdio/netbird/util"
|
||||||
|
"github.com/netbirdio/netbird/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ManagementLegacyPort is the port that was used before by the Management gRPC server.
|
// ManagementLegacyPort is the port that was used before by the Management gRPC server.
|
||||||
@@ -166,7 +167,7 @@ var (
|
|||||||
|
|
||||||
geo, err := geolocation.NewGeolocation(config.Datadir)
|
geo, err := geolocation.NewGeolocation(config.Datadir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("could not initialize geo location service, we proceed without geo support")
|
log.Warnf("could not initialize geo location service: %v, we proceed without geo support", err)
|
||||||
} else {
|
} else {
|
||||||
log.Infof("geo location service has been initialized from %s", config.Datadir)
|
log.Infof("geo location service has been initialized from %s", config.Datadir)
|
||||||
}
|
}
|
||||||
@@ -315,6 +316,7 @@ var (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Infof("management server version %s", version.NetbirdVersion())
|
||||||
log.Infof("running HTTP server and gRPC server on the same port: %s", listener.Addr().String())
|
log.Infof("running HTTP server and gRPC server on the same port: %s", listener.Addr().String())
|
||||||
serveGRPCWithHTTP(listener, rootHandler, tlsEnabled)
|
serveGRPCWithHTTP(listener, rootHandler, tlsEnabled)
|
||||||
|
|
||||||
|
|||||||
@@ -92,6 +92,14 @@ message PeerKeys {
|
|||||||
bytes wgPubKey = 2;
|
bytes wgPubKey = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Environment is part of the PeerSystemMeta and describes the environment the agent is running in.
|
||||||
|
message Environment {
|
||||||
|
// cloud is the cloud provider the agent is running in if applicable.
|
||||||
|
string cloud = 1;
|
||||||
|
// platform is the platform the agent is running on if applicable.
|
||||||
|
string platform = 2;
|
||||||
|
}
|
||||||
|
|
||||||
// PeerSystemMeta is machine meta data like OS and version.
|
// PeerSystemMeta is machine meta data like OS and version.
|
||||||
message PeerSystemMeta {
|
message PeerSystemMeta {
|
||||||
string hostname = 1;
|
string hostname = 1;
|
||||||
@@ -108,6 +116,7 @@ message PeerSystemMeta {
|
|||||||
string sysSerialNumber = 12;
|
string sysSerialNumber = 12;
|
||||||
string sysProductName = 13;
|
string sysProductName = 13;
|
||||||
string sysManufacturer = 14;
|
string sysManufacturer = 14;
|
||||||
|
Environment environment = 15;
|
||||||
}
|
}
|
||||||
|
|
||||||
message LoginResponse {
|
message LoginResponse {
|
||||||
|
|||||||
641
management/refactor/api/grpc/grpcserver.go
Normal file
@@ -0,0 +1,641 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
pb "github.com/golang/protobuf/proto" // nolint
|
||||||
|
"github.com/golang/protobuf/ptypes/timestamp"
|
||||||
|
"github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/realip"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/encryption"
|
||||||
|
"github.com/netbirdio/netbird/management/proto"
|
||||||
|
"github.com/netbirdio/netbird/management/server/jwtclaims"
|
||||||
|
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
||||||
|
internalStatus "github.com/netbirdio/netbird/management/server/status"
|
||||||
|
"github.com/netbirdio/netbird/management/server/telemetry"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GRPCServer an instance of a Management gRPC API server
|
||||||
|
type GRPCServer struct {
|
||||||
|
accountManager AccountManager
|
||||||
|
wgKey wgtypes.Key
|
||||||
|
proto.UnimplementedManagementServiceServer
|
||||||
|
peersUpdateManager *PeersUpdateManager
|
||||||
|
config *Config
|
||||||
|
turnCredentialsManager TURNCredentialsManager
|
||||||
|
jwtValidator *jwtclaims.JWTValidator
|
||||||
|
jwtClaimsExtractor *jwtclaims.ClaimsExtractor
|
||||||
|
appMetrics telemetry.AppMetrics
|
||||||
|
ephemeralManager *EphemeralManager
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewServer creates a new Management server
|
||||||
|
func NewServer(config *Config, accountManager AccountManager, peersUpdateManager *PeersUpdateManager, turnCredentialsManager TURNCredentialsManager, appMetrics telemetry.AppMetrics, ephemeralManager *EphemeralManager) (*GRPCServer, error) {
|
||||||
|
key, err := wgtypes.GeneratePrivateKey()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var jwtValidator *jwtclaims.JWTValidator
|
||||||
|
|
||||||
|
if config.HttpConfig != nil && config.HttpConfig.AuthIssuer != "" && config.HttpConfig.AuthAudience != "" && validateURL(config.HttpConfig.AuthKeysLocation) {
|
||||||
|
jwtValidator, err = jwtclaims.NewJWTValidator(
|
||||||
|
config.HttpConfig.AuthIssuer,
|
||||||
|
config.GetAuthAudiences(),
|
||||||
|
config.HttpConfig.AuthKeysLocation,
|
||||||
|
config.HttpConfig.IdpSignKeyRefreshEnabled,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, status.Errorf(codes.Internal, "unable to create new jwt middleware, err: %v", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var audience, userIDClaim string
|
||||||
|
if config.HttpConfig != nil {
|
||||||
|
audience = config.HttpConfig.AuthAudience
|
||||||
|
userIDClaim = config.HttpConfig.AuthUserIDClaim
|
||||||
|
}
|
||||||
|
jwtClaimsExtractor := jwtclaims.NewClaimsExtractor(
|
||||||
|
jwtclaims.WithAudience(audience),
|
||||||
|
jwtclaims.WithUserIDClaim(userIDClaim),
|
||||||
|
)
|
||||||
|
|
||||||
|
return &GRPCServer{
|
||||||
|
wgKey: key,
|
||||||
|
// peerKey -> event channel
|
||||||
|
peersUpdateManager: peersUpdateManager,
|
||||||
|
accountManager: accountManager,
|
||||||
|
config: config,
|
||||||
|
turnCredentialsManager: turnCredentialsManager,
|
||||||
|
jwtValidator: jwtValidator,
|
||||||
|
jwtClaimsExtractor: jwtClaimsExtractor,
|
||||||
|
appMetrics: appMetrics,
|
||||||
|
ephemeralManager: ephemeralManager,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *GRPCServer) GetServerKey(ctx context.Context, req *proto.Empty) (*proto.ServerKeyResponse, error) {
|
||||||
|
// 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)
|
||||||
|
secs := int64(now.Second())
|
||||||
|
nanos := int32(now.Nanosecond())
|
||||||
|
expiresAt := ×tamp.Timestamp{Seconds: secs, Nanos: nanos}
|
||||||
|
|
||||||
|
return &proto.ServerKeyResponse{
|
||||||
|
Key: s.wgKey.PublicKey().String(),
|
||||||
|
ExpiresAt: expiresAt,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getRealIP(ctx context.Context) net.IP {
|
||||||
|
if addr, ok := realip.FromContext(ctx); ok {
|
||||||
|
return net.IP(addr.AsSlice())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sync validates the existence of a connecting peer, sends an initial state (all available for the connecting peers) and
|
||||||
|
// notifies the connected peer of any updates (e.g. new peers under the same account)
|
||||||
|
func (s *GRPCServer) Sync(req *proto.EncryptedMessage, srv proto.ManagementService_SyncServer) error {
|
||||||
|
reqStart := time.Now()
|
||||||
|
if s.appMetrics != nil {
|
||||||
|
s.appMetrics.GRPCMetrics().CountSyncRequest()
|
||||||
|
}
|
||||||
|
realIP := getRealIP(srv.Context())
|
||||||
|
log.Debugf("Sync request from peer [%s] [%s]", req.WgPubKey, realIP.String())
|
||||||
|
|
||||||
|
syncReq := &proto.SyncRequest{}
|
||||||
|
peerKey, err := s.parseRequest(req, syncReq)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
peer, netMap, err := s.accountManager.SyncPeer(PeerSync{WireGuardPubKey: peerKey.String()})
|
||||||
|
if err != nil {
|
||||||
|
return mapError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = s.sendInitialSync(peerKey, peer, netMap, srv)
|
||||||
|
if err != nil {
|
||||||
|
log.Debugf("error while sending initial sync for %s: %v", peerKey.String(), err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
updates := s.peersUpdateManager.CreateChannel(peer.ID)
|
||||||
|
|
||||||
|
s.ephemeralManager.OnPeerConnected(peer)
|
||||||
|
|
||||||
|
err = s.accountManager.MarkPeerConnected(peerKey.String(), true, realIP)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("failed marking peer as connected %s %v", peerKey, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.config.TURNConfig.TimeBasedCredentials {
|
||||||
|
s.turnCredentialsManager.SetupRefresh(peer.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.appMetrics != nil {
|
||||||
|
s.appMetrics.GRPCMetrics().CountSyncRequestDuration(time.Since(reqStart))
|
||||||
|
}
|
||||||
|
|
||||||
|
// keep a connection to the peer and send updates when available
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
// condition when there are some updates
|
||||||
|
case update, open := <-updates:
|
||||||
|
|
||||||
|
if s.appMetrics != nil {
|
||||||
|
s.appMetrics.GRPCMetrics().UpdateChannelQueueLength(len(updates) + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !open {
|
||||||
|
log.Debugf("updates channel for peer %s was closed", peerKey.String())
|
||||||
|
s.cancelPeerRoutines(peer)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
log.Debugf("received an update for peer %s", peerKey.String())
|
||||||
|
|
||||||
|
encryptedResp, err := encryption.EncryptMessage(peerKey, s.wgKey, update.Update)
|
||||||
|
if err != nil {
|
||||||
|
s.cancelPeerRoutines(peer)
|
||||||
|
return status.Errorf(codes.Internal, "failed processing update message")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = srv.SendMsg(&proto.EncryptedMessage{
|
||||||
|
WgPubKey: s.wgKey.PublicKey().String(),
|
||||||
|
Body: encryptedResp,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
s.cancelPeerRoutines(peer)
|
||||||
|
return status.Errorf(codes.Internal, "failed sending update message")
|
||||||
|
}
|
||||||
|
log.Debugf("sent an update to peer %s", peerKey.String())
|
||||||
|
// condition when client <-> server connection has been terminated
|
||||||
|
case <-srv.Context().Done():
|
||||||
|
// happens when connection drops, e.g. client disconnects
|
||||||
|
log.Debugf("stream of peer %s has been closed", peerKey.String())
|
||||||
|
s.cancelPeerRoutines(peer)
|
||||||
|
return srv.Context().Err()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *GRPCServer) cancelPeerRoutines(peer *nbpeer.Peer) {
|
||||||
|
s.peersUpdateManager.CloseChannel(peer.ID)
|
||||||
|
s.turnCredentialsManager.CancelRefresh(peer.ID)
|
||||||
|
_ = s.accountManager.MarkPeerConnected(peer.Key, false, nil)
|
||||||
|
s.ephemeralManager.OnPeerDisconnected(peer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *GRPCServer) validateToken(jwtToken string) (string, error) {
|
||||||
|
if s.jwtValidator == nil {
|
||||||
|
return "", status.Error(codes.Internal, "no jwt validator set")
|
||||||
|
}
|
||||||
|
|
||||||
|
token, err := s.jwtValidator.ValidateAndParse(jwtToken)
|
||||||
|
if err != nil {
|
||||||
|
return "", status.Errorf(codes.InvalidArgument, "invalid jwt token, err: %v", err)
|
||||||
|
}
|
||||||
|
claims := s.jwtClaimsExtractor.FromToken(token)
|
||||||
|
// we need to call this method because if user is new, we will automatically add it to existing or create a new account
|
||||||
|
_, _, err = s.accountManager.GetAccountFromToken(claims)
|
||||||
|
if err != nil {
|
||||||
|
return "", status.Errorf(codes.Internal, "unable to fetch account with claims, err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.accountManager.CheckUserAccessByJWTGroups(claims); err != nil {
|
||||||
|
return "", status.Errorf(codes.PermissionDenied, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return claims.UserId, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// maps internal internalStatus.Error to gRPC status.Error
|
||||||
|
func mapError(err error) error {
|
||||||
|
if e, ok := internalStatus.FromError(err); ok {
|
||||||
|
switch e.Type() {
|
||||||
|
case internalStatus.PermissionDenied:
|
||||||
|
return status.Errorf(codes.PermissionDenied, e.Message)
|
||||||
|
case internalStatus.Unauthorized:
|
||||||
|
return status.Errorf(codes.PermissionDenied, e.Message)
|
||||||
|
case internalStatus.Unauthenticated:
|
||||||
|
return status.Errorf(codes.PermissionDenied, e.Message)
|
||||||
|
case internalStatus.PreconditionFailed:
|
||||||
|
return status.Errorf(codes.FailedPrecondition, e.Message)
|
||||||
|
case internalStatus.NotFound:
|
||||||
|
return status.Errorf(codes.NotFound, e.Message)
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Errorf("got an unhandled error: %s", err)
|
||||||
|
return status.Errorf(codes.Internal, "failed handling request")
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractPeerMeta(loginReq *proto.LoginRequest) nbpeer.PeerSystemMeta {
|
||||||
|
osVersion := loginReq.GetMeta().GetOSVersion()
|
||||||
|
if osVersion == "" {
|
||||||
|
osVersion = loginReq.GetMeta().GetCore()
|
||||||
|
}
|
||||||
|
|
||||||
|
networkAddresses := make([]nbpeer.NetworkAddress, 0, len(loginReq.GetMeta().GetNetworkAddresses()))
|
||||||
|
for _, addr := range loginReq.GetMeta().GetNetworkAddresses() {
|
||||||
|
netAddr, err := netip.ParsePrefix(addr.GetNetIP())
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("failed to parse netip address, %s: %v", addr.GetNetIP(), err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
networkAddresses = append(networkAddresses, nbpeer.NetworkAddress{
|
||||||
|
NetIP: netAddr,
|
||||||
|
Mac: addr.GetMac(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return nbpeer.PeerSystemMeta{
|
||||||
|
Hostname: loginReq.GetMeta().GetHostname(),
|
||||||
|
GoOS: loginReq.GetMeta().GetGoOS(),
|
||||||
|
Kernel: loginReq.GetMeta().GetKernel(),
|
||||||
|
Platform: loginReq.GetMeta().GetPlatform(),
|
||||||
|
OS: loginReq.GetMeta().GetOS(),
|
||||||
|
OSVersion: osVersion,
|
||||||
|
WtVersion: loginReq.GetMeta().GetWiretrusteeVersion(),
|
||||||
|
UIVersion: loginReq.GetMeta().GetUiVersion(),
|
||||||
|
KernelVersion: loginReq.GetMeta().GetKernelVersion(),
|
||||||
|
NetworkAddresses: networkAddresses,
|
||||||
|
SystemSerialNumber: loginReq.GetMeta().GetSysSerialNumber(),
|
||||||
|
SystemProductName: loginReq.GetMeta().GetSysProductName(),
|
||||||
|
SystemManufacturer: loginReq.GetMeta().GetSysManufacturer(),
|
||||||
|
Environment: nbpeer.Environment{
|
||||||
|
Cloud: loginReq.GetMeta().GetEnvironment().GetCloud(),
|
||||||
|
Platform: loginReq.GetMeta().GetEnvironment().GetPlatform(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *GRPCServer) parseRequest(req *proto.EncryptedMessage, parsed pb.Message) (wgtypes.Key, error) {
|
||||||
|
peerKey, err := wgtypes.ParseKey(req.GetWgPubKey())
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("error while parsing peer's WireGuard public key %s.", req.WgPubKey)
|
||||||
|
return wgtypes.Key{}, status.Errorf(codes.InvalidArgument, "provided wgPubKey %s is invalid", req.WgPubKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = encryption.DecryptMessage(peerKey, s.wgKey, req.Body, parsed)
|
||||||
|
if err != nil {
|
||||||
|
return wgtypes.Key{}, status.Errorf(codes.InvalidArgument, "invalid request message")
|
||||||
|
}
|
||||||
|
|
||||||
|
return peerKey, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Login endpoint first checks whether peer is registered under any account
|
||||||
|
// In case it is, the login is successful
|
||||||
|
// In case it isn't, the endpoint checks whether setup key is provided within the request and tries to register a peer.
|
||||||
|
// In case of the successful registration login is also successful
|
||||||
|
func (s *GRPCServer) Login(ctx context.Context, req *proto.EncryptedMessage) (*proto.EncryptedMessage, error) {
|
||||||
|
reqStart := time.Now()
|
||||||
|
defer func() {
|
||||||
|
if s.appMetrics != nil {
|
||||||
|
s.appMetrics.GRPCMetrics().CountLoginRequestDuration(time.Since(reqStart))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
if s.appMetrics != nil {
|
||||||
|
s.appMetrics.GRPCMetrics().CountLoginRequest()
|
||||||
|
}
|
||||||
|
realIP := getRealIP(ctx)
|
||||||
|
log.Debugf("Login request from peer [%s] [%s]", req.WgPubKey, realIP.String())
|
||||||
|
|
||||||
|
loginReq := &proto.LoginRequest{}
|
||||||
|
peerKey, err := s.parseRequest(req, loginReq)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if loginReq.GetMeta() == nil {
|
||||||
|
msg := status.Errorf(codes.FailedPrecondition,
|
||||||
|
"peer system meta has to be provided to log in. Peer %s, remote addr %s", peerKey.String(), realIP)
|
||||||
|
log.Warn(msg)
|
||||||
|
return nil, msg
|
||||||
|
}
|
||||||
|
|
||||||
|
userID := ""
|
||||||
|
// JWT token is not always provided, it is fine for userID to be empty cuz it might be that peer is already registered,
|
||||||
|
// or it uses a setup key to register.
|
||||||
|
if loginReq.GetJwtToken() != "" {
|
||||||
|
userID, err = s.validateToken(loginReq.GetJwtToken())
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("failed validating JWT token sent from peer %s", peerKey)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var sshKey []byte
|
||||||
|
if loginReq.GetPeerKeys() != nil {
|
||||||
|
sshKey = loginReq.GetPeerKeys().GetSshPubKey()
|
||||||
|
}
|
||||||
|
|
||||||
|
peer, netMap, err := s.accountManager.LoginPeer(PeerLogin{
|
||||||
|
WireGuardPubKey: peerKey.String(),
|
||||||
|
SSHKey: string(sshKey),
|
||||||
|
Meta: extractPeerMeta(loginReq),
|
||||||
|
UserID: userID,
|
||||||
|
SetupKey: loginReq.GetSetupKey(),
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("failed logging in peer %s", peerKey)
|
||||||
|
return nil, mapError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the login request contains setup key then it is a registration request
|
||||||
|
if loginReq.GetSetupKey() != "" {
|
||||||
|
s.ephemeralManager.OnPeerDisconnected(peer)
|
||||||
|
}
|
||||||
|
|
||||||
|
// if peer has reached this point then it has logged in
|
||||||
|
loginResp := &proto.LoginResponse{
|
||||||
|
WiretrusteeConfig: toWiretrusteeConfig(s.config, nil),
|
||||||
|
PeerConfig: toPeerConfig(peer, netMap.Network, s.accountManager.GetDNSDomain()),
|
||||||
|
}
|
||||||
|
encryptedResp, err := encryption.EncryptMessage(peerKey, s.wgKey, loginResp)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("failed encrypting peer %s message", peer.ID)
|
||||||
|
return nil, status.Errorf(codes.Internal, "failed logging in peer")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &proto.EncryptedMessage{
|
||||||
|
WgPubKey: s.wgKey.PublicKey().String(),
|
||||||
|
Body: encryptedResp,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToResponseProto(configProto Protocol) proto.HostConfig_Protocol {
|
||||||
|
switch configProto {
|
||||||
|
case UDP:
|
||||||
|
return proto.HostConfig_UDP
|
||||||
|
case DTLS:
|
||||||
|
return proto.HostConfig_DTLS
|
||||||
|
case HTTP:
|
||||||
|
return proto.HostConfig_HTTP
|
||||||
|
case HTTPS:
|
||||||
|
return proto.HostConfig_HTTPS
|
||||||
|
case TCP:
|
||||||
|
return proto.HostConfig_TCP
|
||||||
|
default:
|
||||||
|
panic(fmt.Errorf("unexpected config protocol type %v", configProto))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func toWiretrusteeConfig(config *Config, turnCredentials *TURNCredentials) *proto.WiretrusteeConfig {
|
||||||
|
if config == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var stuns []*proto.HostConfig
|
||||||
|
for _, stun := range config.Stuns {
|
||||||
|
stuns = append(stuns, &proto.HostConfig{
|
||||||
|
Uri: stun.URI,
|
||||||
|
Protocol: ToResponseProto(stun.Proto),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
var turns []*proto.ProtectedHostConfig
|
||||||
|
for _, turn := range config.TURNConfig.Turns {
|
||||||
|
var username string
|
||||||
|
var password string
|
||||||
|
if turnCredentials != nil {
|
||||||
|
username = turnCredentials.Username
|
||||||
|
password = turnCredentials.Password
|
||||||
|
} else {
|
||||||
|
username = turn.Username
|
||||||
|
password = turn.Password
|
||||||
|
}
|
||||||
|
turns = append(turns, &proto.ProtectedHostConfig{
|
||||||
|
HostConfig: &proto.HostConfig{
|
||||||
|
Uri: turn.URI,
|
||||||
|
Protocol: ToResponseProto(turn.Proto),
|
||||||
|
},
|
||||||
|
User: username,
|
||||||
|
Password: password,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return &proto.WiretrusteeConfig{
|
||||||
|
Stuns: stuns,
|
||||||
|
Turns: turns,
|
||||||
|
Signal: &proto.HostConfig{
|
||||||
|
Uri: config.Signal.URI,
|
||||||
|
Protocol: ToResponseProto(config.Signal.Proto),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func toPeerConfig(peer *nbpeer.Peer, network *Network, dnsName string) *proto.PeerConfig {
|
||||||
|
netmask, _ := network.Net.Mask.Size()
|
||||||
|
fqdn := peer.FQDN(dnsName)
|
||||||
|
return &proto.PeerConfig{
|
||||||
|
Address: fmt.Sprintf("%s/%d", peer.IP.String(), netmask), // take it from the network
|
||||||
|
SshConfig: &proto.SSHConfig{SshEnabled: peer.SSHEnabled},
|
||||||
|
Fqdn: fqdn,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func toRemotePeerConfig(peers []*nbpeer.Peer, dnsName string) []*proto.RemotePeerConfig {
|
||||||
|
remotePeers := []*proto.RemotePeerConfig{}
|
||||||
|
for _, rPeer := range peers {
|
||||||
|
fqdn := rPeer.FQDN(dnsName)
|
||||||
|
remotePeers = append(remotePeers, &proto.RemotePeerConfig{
|
||||||
|
WgPubKey: rPeer.Key,
|
||||||
|
AllowedIps: []string{fmt.Sprintf(AllowedIPsFormat, rPeer.IP)},
|
||||||
|
SshConfig: &proto.SSHConfig{SshPubKey: []byte(rPeer.SSHKey)},
|
||||||
|
Fqdn: fqdn,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return remotePeers
|
||||||
|
}
|
||||||
|
|
||||||
|
func toSyncResponse(config *Config, peer *nbpeer.Peer, turnCredentials *TURNCredentials, networkMap *NetworkMap, dnsName string) *proto.SyncResponse {
|
||||||
|
wtConfig := toWiretrusteeConfig(config, turnCredentials)
|
||||||
|
|
||||||
|
pConfig := toPeerConfig(peer, networkMap.Network, dnsName)
|
||||||
|
|
||||||
|
remotePeers := toRemotePeerConfig(networkMap.Peers, dnsName)
|
||||||
|
|
||||||
|
routesUpdate := toProtocolRoutes(networkMap.Routes)
|
||||||
|
|
||||||
|
dnsUpdate := toProtocolDNSConfig(networkMap.DNSConfig)
|
||||||
|
|
||||||
|
offlinePeers := toRemotePeerConfig(networkMap.OfflinePeers, dnsName)
|
||||||
|
|
||||||
|
firewallRules := toProtocolFirewallRules(networkMap.FirewallRules)
|
||||||
|
|
||||||
|
return &proto.SyncResponse{
|
||||||
|
WiretrusteeConfig: wtConfig,
|
||||||
|
PeerConfig: pConfig,
|
||||||
|
RemotePeers: remotePeers,
|
||||||
|
RemotePeersIsEmpty: len(remotePeers) == 0,
|
||||||
|
NetworkMap: &proto.NetworkMap{
|
||||||
|
Serial: networkMap.Network.CurrentSerial(),
|
||||||
|
PeerConfig: pConfig,
|
||||||
|
RemotePeers: remotePeers,
|
||||||
|
OfflinePeers: offlinePeers,
|
||||||
|
RemotePeersIsEmpty: len(remotePeers) == 0,
|
||||||
|
Routes: routesUpdate,
|
||||||
|
DNSConfig: dnsUpdate,
|
||||||
|
FirewallRules: firewallRules,
|
||||||
|
FirewallRulesIsEmpty: len(firewallRules) == 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsHealthy indicates whether the service is healthy
|
||||||
|
func (s *GRPCServer) IsHealthy(ctx context.Context, req *proto.Empty) (*proto.Empty, error) {
|
||||||
|
return &proto.Empty{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendInitialSync sends initial proto.SyncResponse to the peer requesting synchronization
|
||||||
|
func (s *GRPCServer) sendInitialSync(peerKey wgtypes.Key, peer *nbpeer.Peer, networkMap *NetworkMap, srv proto.ManagementService_SyncServer) error {
|
||||||
|
// make secret time based TURN credentials optional
|
||||||
|
var turnCredentials *TURNCredentials
|
||||||
|
if s.config.TURNConfig.TimeBasedCredentials {
|
||||||
|
creds := s.turnCredentialsManager.GenerateCredentials()
|
||||||
|
turnCredentials = &creds
|
||||||
|
} else {
|
||||||
|
turnCredentials = nil
|
||||||
|
}
|
||||||
|
plainResp := toSyncResponse(s.config, peer, turnCredentials, networkMap, s.accountManager.GetDNSDomain())
|
||||||
|
|
||||||
|
encryptedResp, err := encryption.EncryptMessage(peerKey, s.wgKey, plainResp)
|
||||||
|
if err != nil {
|
||||||
|
return status.Errorf(codes.Internal, "error handling request")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = srv.Send(&proto.EncryptedMessage{
|
||||||
|
WgPubKey: s.wgKey.PublicKey().String(),
|
||||||
|
Body: encryptedResp,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed sending SyncResponse %v", err)
|
||||||
|
return status.Errorf(codes.Internal, "error handling request")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDeviceAuthorizationFlow returns a device authorization flow information
|
||||||
|
// This is used for initiating an Oauth 2 device authorization grant flow
|
||||||
|
// which will be used by our clients to Login
|
||||||
|
func (s *GRPCServer) GetDeviceAuthorizationFlow(ctx context.Context, req *proto.EncryptedMessage) (*proto.EncryptedMessage, error) {
|
||||||
|
peerKey, err := wgtypes.ParseKey(req.GetWgPubKey())
|
||||||
|
if err != nil {
|
||||||
|
errMSG := fmt.Sprintf("error while parsing peer's Wireguard public key %s on GetDeviceAuthorizationFlow request.", req.WgPubKey)
|
||||||
|
log.Warn(errMSG)
|
||||||
|
return nil, status.Error(codes.InvalidArgument, errMSG)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = encryption.DecryptMessage(peerKey, s.wgKey, req.Body, &proto.DeviceAuthorizationFlowRequest{})
|
||||||
|
if err != nil {
|
||||||
|
errMSG := fmt.Sprintf("error while decrypting peer's message with Wireguard public key %s.", req.WgPubKey)
|
||||||
|
log.Warn(errMSG)
|
||||||
|
return nil, status.Error(codes.InvalidArgument, errMSG)
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.config.DeviceAuthorizationFlow == nil || s.config.DeviceAuthorizationFlow.Provider == string(NONE) {
|
||||||
|
return nil, status.Error(codes.NotFound, "no device authorization flow information available")
|
||||||
|
}
|
||||||
|
|
||||||
|
provider, ok := proto.DeviceAuthorizationFlowProvider_value[strings.ToUpper(s.config.DeviceAuthorizationFlow.Provider)]
|
||||||
|
if !ok {
|
||||||
|
return nil, status.Errorf(codes.InvalidArgument, "no provider found in the protocol for %s", s.config.DeviceAuthorizationFlow.Provider)
|
||||||
|
}
|
||||||
|
|
||||||
|
flowInfoResp := &proto.DeviceAuthorizationFlow{
|
||||||
|
Provider: proto.DeviceAuthorizationFlowProvider(provider),
|
||||||
|
ProviderConfig: &proto.ProviderConfig{
|
||||||
|
ClientID: s.config.DeviceAuthorizationFlow.ProviderConfig.ClientID,
|
||||||
|
ClientSecret: s.config.DeviceAuthorizationFlow.ProviderConfig.ClientSecret,
|
||||||
|
Domain: s.config.DeviceAuthorizationFlow.ProviderConfig.Domain,
|
||||||
|
Audience: s.config.DeviceAuthorizationFlow.ProviderConfig.Audience,
|
||||||
|
DeviceAuthEndpoint: s.config.DeviceAuthorizationFlow.ProviderConfig.DeviceAuthEndpoint,
|
||||||
|
TokenEndpoint: s.config.DeviceAuthorizationFlow.ProviderConfig.TokenEndpoint,
|
||||||
|
Scope: s.config.DeviceAuthorizationFlow.ProviderConfig.Scope,
|
||||||
|
UseIDToken: s.config.DeviceAuthorizationFlow.ProviderConfig.UseIDToken,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
encryptedResp, err := encryption.EncryptMessage(peerKey, s.wgKey, flowInfoResp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, status.Error(codes.Internal, "failed to encrypt no device authorization flow information")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &proto.EncryptedMessage{
|
||||||
|
WgPubKey: s.wgKey.PublicKey().String(),
|
||||||
|
Body: encryptedResp,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPKCEAuthorizationFlow returns a pkce authorization flow information
|
||||||
|
// This is used for initiating an Oauth 2 pkce authorization grant flow
|
||||||
|
// which will be used by our clients to Login
|
||||||
|
func (s *GRPCServer) GetPKCEAuthorizationFlow(_ context.Context, req *proto.EncryptedMessage) (*proto.EncryptedMessage, error) {
|
||||||
|
peerKey, err := wgtypes.ParseKey(req.GetWgPubKey())
|
||||||
|
if err != nil {
|
||||||
|
errMSG := fmt.Sprintf("error while parsing peer's Wireguard public key %s on GetPKCEAuthorizationFlow request.", req.WgPubKey)
|
||||||
|
log.Warn(errMSG)
|
||||||
|
return nil, status.Error(codes.InvalidArgument, errMSG)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = encryption.DecryptMessage(peerKey, s.wgKey, req.Body, &proto.PKCEAuthorizationFlowRequest{})
|
||||||
|
if err != nil {
|
||||||
|
errMSG := fmt.Sprintf("error while decrypting peer's message with Wireguard public key %s.", req.WgPubKey)
|
||||||
|
log.Warn(errMSG)
|
||||||
|
return nil, status.Error(codes.InvalidArgument, errMSG)
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.config.PKCEAuthorizationFlow == nil {
|
||||||
|
return nil, status.Error(codes.NotFound, "no pkce authorization flow information available")
|
||||||
|
}
|
||||||
|
|
||||||
|
flowInfoResp := &proto.PKCEAuthorizationFlow{
|
||||||
|
ProviderConfig: &proto.ProviderConfig{
|
||||||
|
Audience: s.config.PKCEAuthorizationFlow.ProviderConfig.Audience,
|
||||||
|
ClientID: s.config.PKCEAuthorizationFlow.ProviderConfig.ClientID,
|
||||||
|
ClientSecret: s.config.PKCEAuthorizationFlow.ProviderConfig.ClientSecret,
|
||||||
|
TokenEndpoint: s.config.PKCEAuthorizationFlow.ProviderConfig.TokenEndpoint,
|
||||||
|
AuthorizationEndpoint: s.config.PKCEAuthorizationFlow.ProviderConfig.AuthorizationEndpoint,
|
||||||
|
Scope: s.config.PKCEAuthorizationFlow.ProviderConfig.Scope,
|
||||||
|
RedirectURLs: s.config.PKCEAuthorizationFlow.ProviderConfig.RedirectURLs,
|
||||||
|
UseIDToken: s.config.PKCEAuthorizationFlow.ProviderConfig.UseIDToken,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
encryptedResp, err := encryption.EncryptMessage(peerKey, s.wgKey, flowInfoResp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, status.Error(codes.Internal, "failed to encrypt no pkce authorization flow information")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &proto.EncryptedMessage{
|
||||||
|
WgPubKey: s.wgKey.PublicKey().String(),
|
||||||
|
Body: encryptedResp,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
122
management/refactor/api/http/handler.go
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/rs/cors"
|
||||||
|
|
||||||
|
"github.com/netbirdio/management-integrations/integrations"
|
||||||
|
"github.com/netbirdio/netbird/management/refactor/resources/peers"
|
||||||
|
s "github.com/netbirdio/netbird/management/server"
|
||||||
|
"github.com/netbirdio/netbird/management/server/geolocation"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/management/server/http/middleware"
|
||||||
|
"github.com/netbirdio/netbird/management/server/jwtclaims"
|
||||||
|
"github.com/netbirdio/netbird/management/server/telemetry"
|
||||||
|
)
|
||||||
|
|
||||||
|
const apiPrefix = "/api"
|
||||||
|
|
||||||
|
// AuthCfg contains parameters for authentication middleware
|
||||||
|
type AuthCfg struct {
|
||||||
|
Issuer string
|
||||||
|
Audience string
|
||||||
|
UserIDClaim string
|
||||||
|
KeysLocation string
|
||||||
|
}
|
||||||
|
|
||||||
|
type DefaultAPIHandler struct {
|
||||||
|
Router *mux.Router
|
||||||
|
AccountManager s.AccountManager
|
||||||
|
geolocationManager *geolocation.Geolocation
|
||||||
|
AuthCfg AuthCfg
|
||||||
|
}
|
||||||
|
|
||||||
|
// EmptyObject is an empty struct used to return empty JSON object
|
||||||
|
type EmptyObject struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDefaultAPIHandler creates the Management service HTTP API handler registering all the available endpoints.
|
||||||
|
func NewDefaultAPIHandler(ctx context.Context, jwtValidator jwtclaims.JWTValidator, appMetrics telemetry.AppMetrics, authCfg AuthCfg) (http.Handler, error) {
|
||||||
|
claimsExtractor := jwtclaims.NewClaimsExtractor(
|
||||||
|
jwtclaims.WithAudience(authCfg.Audience),
|
||||||
|
jwtclaims.WithUserIDClaim(authCfg.UserIDClaim),
|
||||||
|
)
|
||||||
|
|
||||||
|
authMiddleware := middleware.NewAuthMiddleware(
|
||||||
|
accountManager.GetAccountFromPAT,
|
||||||
|
jwtValidator.ValidateAndParse,
|
||||||
|
accountManager.MarkPATUsed,
|
||||||
|
accountManager.CheckUserAccessByJWTGroups,
|
||||||
|
claimsExtractor,
|
||||||
|
authCfg.Audience,
|
||||||
|
authCfg.UserIDClaim,
|
||||||
|
)
|
||||||
|
|
||||||
|
corsMiddleware := cors.AllowAll()
|
||||||
|
|
||||||
|
acMiddleware := middleware.NewAccessControl(
|
||||||
|
authCfg.Audience,
|
||||||
|
authCfg.UserIDClaim,
|
||||||
|
accountManager.GetUser)
|
||||||
|
|
||||||
|
rootRouter := mux.NewRouter()
|
||||||
|
metricsMiddleware := appMetrics.HTTPMiddleware()
|
||||||
|
|
||||||
|
prefix := apiPrefix
|
||||||
|
router := rootRouter.PathPrefix(prefix).Subrouter()
|
||||||
|
router.Use(metricsMiddleware.Handler, corsMiddleware.Handler, authMiddleware.Handler, acMiddleware.Handler)
|
||||||
|
|
||||||
|
api := DefaultAPIHandler{
|
||||||
|
Router: router,
|
||||||
|
AccountManager: accountManager,
|
||||||
|
geolocationManager: LocationManager,
|
||||||
|
AuthCfg: authCfg,
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := integrations.RegisterHandlers(ctx, prefix, api.Router, accountManager, claimsExtractor); err != nil {
|
||||||
|
return nil, fmt.Errorf("register integrations endpoints: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
peers.RegisterPeersEndpoints(api.Router)
|
||||||
|
// api.addAccountsEndpoint()
|
||||||
|
// api.addPeersEndpoint()
|
||||||
|
// api.addUsersEndpoint()
|
||||||
|
// api.addUsersTokensEndpoint()
|
||||||
|
// api.addSetupKeysEndpoint()
|
||||||
|
// api.addRulesEndpoint()
|
||||||
|
// api.addPoliciesEndpoint()
|
||||||
|
// api.addGroupsEndpoint()
|
||||||
|
// api.addRoutesEndpoint()
|
||||||
|
// api.addDNSNameserversEndpoint()
|
||||||
|
// api.addDNSSettingEndpoint()
|
||||||
|
// api.addEventsEndpoint()
|
||||||
|
// api.addPostureCheckEndpoint()
|
||||||
|
// api.addLocationsEndpoint()
|
||||||
|
|
||||||
|
err := api.Router.Walk(func(route *mux.Route, _ *mux.Router, _ []*mux.Route) error {
|
||||||
|
methods, err := route.GetMethods()
|
||||||
|
if err != nil { // we may have wildcard routes from integrations without methods, skip them for now
|
||||||
|
methods = []string{}
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
7
management/refactor/api/http/specs/cfg.yaml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package: api
|
||||||
|
generate:
|
||||||
|
models: true
|
||||||
|
embedded-spec: false
|
||||||
|
output: types.gen.go
|
||||||
|
compatibility:
|
||||||
|
always-prefix-enum-values: true
|
||||||
16
management/refactor/api/http/specs/generate.sh
Executable file
@@ -0,0 +1,16 @@
|
|||||||
|
#!/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 github.com/deepmap/oapi-codegen/cmd/oapi-codegen@4a1477f6a8ba6ca8115cc23bb2fb67f0b9fca18e
|
||||||
|
oapi-codegen --config cfg.yaml openapi.yml
|
||||||
|
cd "$old_pwd"
|
||||||
2870
management/refactor/api/http/specs/openapi.yaml
Normal file
1
management/refactor/api/http/specs/types.gen.go
Normal file
@@ -0,0 +1 @@
|
|||||||
|
package specs
|
||||||
178
management/refactor/mesh/controller.go
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
package mesh
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/netbirdio/management-integrations/integrations"
|
||||||
|
nbdns "github.com/netbirdio/netbird/dns"
|
||||||
|
"github.com/netbirdio/netbird/management/refactor/api/http"
|
||||||
|
"github.com/netbirdio/netbird/management/refactor/resources/network"
|
||||||
|
networkTypes "github.com/netbirdio/netbird/management/refactor/resources/network/types"
|
||||||
|
"github.com/netbirdio/netbird/management/refactor/resources/peers"
|
||||||
|
peerTypes "github.com/netbirdio/netbird/management/refactor/resources/peers/types"
|
||||||
|
"github.com/netbirdio/netbird/management/refactor/resources/policies"
|
||||||
|
"github.com/netbirdio/netbird/management/refactor/resources/routes"
|
||||||
|
"github.com/netbirdio/netbird/management/refactor/resources/settings"
|
||||||
|
"github.com/netbirdio/netbird/management/refactor/resources/users"
|
||||||
|
"github.com/netbirdio/netbird/management/refactor/store"
|
||||||
|
"github.com/netbirdio/netbird/management/server/activity"
|
||||||
|
"github.com/netbirdio/netbird/management/server/status"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Controller interface {
|
||||||
|
LoginPeer()
|
||||||
|
SyncPeer()
|
||||||
|
}
|
||||||
|
|
||||||
|
type DefaultController struct {
|
||||||
|
store store.Store
|
||||||
|
peersManager peers.Manager
|
||||||
|
userManager users.Manager
|
||||||
|
policiesManager policies.Manager
|
||||||
|
settingsManager settings.Manager
|
||||||
|
networkManager network.Manager
|
||||||
|
routesManager routes.Manager
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDefaultController() *DefaultController {
|
||||||
|
storeStore, _ := store.NewDefaultStore(store.SqliteStoreEngine, "", nil)
|
||||||
|
settingsManager := settings.NewDefaultManager(storeStore)
|
||||||
|
networkManager := network.NewDefaultManager()
|
||||||
|
peersManager := peers.NewDefaultManager(storeStore, settingsManager)
|
||||||
|
routesManager := routes.NewDefaultManager(storeStore, peersManager)
|
||||||
|
usersManager := users.NewDefaultManager(storeStore, peersManager)
|
||||||
|
policiesManager := policies.NewDefaultManager(storeStore, peersManager)
|
||||||
|
|
||||||
|
apiHandler, _ := http.NewDefaultAPIHandler()
|
||||||
|
|
||||||
|
peersManager, settingsManager, usersManager, policiesManager, storeStore, apiHandler = integrations.InjectCloud(peersManager, policiesManager, settingsManager, usersManager, storeStore)
|
||||||
|
|
||||||
|
return &DefaultController{
|
||||||
|
store: storeStore,
|
||||||
|
peersManager: peersManager,
|
||||||
|
userManager: usersManager,
|
||||||
|
policiesManager: policiesManager,
|
||||||
|
settingsManager: settingsManager,
|
||||||
|
networkManager: networkManager,
|
||||||
|
routesManager: routesManager,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *DefaultController) LoginPeer(login peerTypes.PeerLogin) (*peerTypes.Peer, *networkTypes.NetworkMap, error) {
|
||||||
|
|
||||||
|
peer, err := c.peersManager.GetPeerByPubKey(login.WireGuardPubKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, status.Errorf(status.Unauthenticated, "peer is not registered")
|
||||||
|
}
|
||||||
|
|
||||||
|
if peer.AddedWithSSOLogin() {
|
||||||
|
user, err := c.userManager.GetUser(peer.GetUserID())
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if user.IsBlocked() {
|
||||||
|
return nil, nil, status.Errorf(status.PermissionDenied, "user is blocked")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
settings, err := c.settingsManager.GetSettings(peer.GetAccountID())
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// this flag prevents unnecessary calls to the persistent store.
|
||||||
|
shouldStorePeer := false
|
||||||
|
updateRemotePeers := false
|
||||||
|
if peerLoginExpired(peer, settings) {
|
||||||
|
err = checkAuth(login.UserID, peer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
// If peer was expired before and if it reached this point, it is re-authenticated.
|
||||||
|
// UserID is present, meaning that JWT validation passed successfully in the API layer.
|
||||||
|
peer.UpdateLastLogin()
|
||||||
|
updateRemotePeers = true
|
||||||
|
shouldStorePeer = true
|
||||||
|
|
||||||
|
pm.eventsManager.StoreEvent(login.UserID, peer.GetID(), peer.GetAccountID(), activity.UserLoggedInPeer, peer.EventMeta(pm.accountManager.GetDNSDomain()))
|
||||||
|
}
|
||||||
|
|
||||||
|
if peer.UpdateMetaIfNew(login.Meta) {
|
||||||
|
shouldStorePeer = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if peer.CheckAndUpdatePeerSSHKey(login.SSHKey) {
|
||||||
|
shouldStorePeer = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if shouldStorePeer {
|
||||||
|
err := pm.repository.updatePeer(peer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if updateRemotePeers {
|
||||||
|
am.updateAccountPeers(account)
|
||||||
|
}
|
||||||
|
return peer, account.GetPeerNetworkMap(peer.ID, pm.accountManager.GetDNSDomain()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *DefaultController) SyncPeer() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *DefaultController) GetPeerNetworkMap(accountID, peerID, dnsDomain string) (*networkTypes.NetworkMap, error) {
|
||||||
|
unlock := c.store.AcquireAccountLock(accountID)
|
||||||
|
defer unlock()
|
||||||
|
|
||||||
|
network, err := c.networkManager.GetNetwork(accountID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
peer, err := c.peersManager.GetNetworkPeerByID(peerID)
|
||||||
|
if err != nil {
|
||||||
|
return &networkTypes.NetworkMap{
|
||||||
|
Network: network.Copy(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
aclPeers, firewallRules := c.policiesManager.GetAccessiblePeersAndFirewallRules(peerID)
|
||||||
|
// exclude expired peers
|
||||||
|
var peersToConnect []*peerTypes.Peer
|
||||||
|
var expiredPeers []*peerTypes.Peer
|
||||||
|
accSettings, _ := c.settingsManager.GetSettings(peer.GetAccountID())
|
||||||
|
for _, p := range aclPeers {
|
||||||
|
expired, _ := p.LoginExpired(accSettings.GetPeerLoginExpiration())
|
||||||
|
if accSettings.GetPeerLoginExpirationEnabled() && expired {
|
||||||
|
expiredPeers = append(expiredPeers, &p)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
peersToConnect = append(peersToConnect, &p)
|
||||||
|
}
|
||||||
|
|
||||||
|
routesUpdate := c.routesManager.GetRoutesToSync(peerID, peersToConnect, accountID)
|
||||||
|
|
||||||
|
dnsManagementStatus := a.getPeerDNSManagementStatus(peerID)
|
||||||
|
dnsUpdate := nbdns.Config{
|
||||||
|
ServiceEnable: dnsManagementStatus,
|
||||||
|
}
|
||||||
|
|
||||||
|
if dnsManagementStatus {
|
||||||
|
var zones []nbdns.CustomZone
|
||||||
|
peersCustomZone := getPeersCustomZone(a, dnsDomain)
|
||||||
|
if peersCustomZone.Domain != "" {
|
||||||
|
zones = append(zones, peersCustomZone)
|
||||||
|
}
|
||||||
|
dnsUpdate.CustomZones = zones
|
||||||
|
dnsUpdate.NameServerGroups = getPeerNSGroups(a, peerID)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &networkTypes.NetworkMap{
|
||||||
|
Peers: peersToConnect,
|
||||||
|
Network: network.Copy(),
|
||||||
|
Routes: routesUpdate,
|
||||||
|
DNSConfig: dnsUpdate,
|
||||||
|
OfflinePeers: expiredPeers,
|
||||||
|
FirewallRules: firewallRules,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
1
management/refactor/resources/dns/api.go
Normal file
@@ -0,0 +1 @@
|
|||||||
|
package dns
|
||||||
1
management/refactor/resources/dns/manager.go
Normal file
@@ -0,0 +1 @@
|
|||||||
|
package dns
|
||||||
1
management/refactor/resources/dns/repository.go
Normal file
@@ -0,0 +1 @@
|
|||||||
|
package dns
|
||||||
11
management/refactor/resources/dns/types/config.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
9
management/refactor/resources/dns/types/custom_zone.go
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
75
management/refactor/resources/dns/types/name_server.go
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import "net/netip"
|
||||||
|
|
||||||
|
// NameServerType nameserver type
|
||||||
|
type NameServerType int
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
65
management/refactor/resources/dns/types/name_server_group.go
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
type NameServerGroup interface {
|
||||||
|
}
|
||||||
|
|
||||||
|
type DefaultNameServerGroup struct {
|
||||||
|
// ID identifier of group
|
||||||
|
ID string `gorm:"primaryKey"`
|
||||||
|
// AccountID is a reference to Account that this object belongs
|
||||||
|
AccountID string `gorm:"index"`
|
||||||
|
// Name group name
|
||||||
|
Name string
|
||||||
|
// Description group description
|
||||||
|
Description string
|
||||||
|
// NameServers list of nameservers
|
||||||
|
NameServers []NameServer `gorm:"serializer:json"`
|
||||||
|
// Groups list of peer group IDs to distribute the nameservers information
|
||||||
|
Groups []string `gorm:"serializer:json"`
|
||||||
|
// 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 `gorm:"serializer:json"`
|
||||||
|
// Enabled group status
|
||||||
|
Enabled bool
|
||||||
|
// SearchDomainsEnabled indicates whether to add match domains to search domains list or not
|
||||||
|
SearchDomainsEnabled bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// EventMeta returns activity event meta related to the nameserver group
|
||||||
|
func (g *DefaultNameServerGroup) EventMeta() map[string]any {
|
||||||
|
return map[string]any{"name": g.Name}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy copies a nameserver group object
|
||||||
|
func (g *DefaultNameServerGroup) Copy() *DefaultNameServerGroup {
|
||||||
|
nsGroup := &DefaultNameServerGroup{
|
||||||
|
ID: g.ID,
|
||||||
|
Name: g.Name,
|
||||||
|
Description: g.Description,
|
||||||
|
NameServers: make([]NameServer, len(g.NameServers)),
|
||||||
|
Groups: make([]string, len(g.Groups)),
|
||||||
|
Enabled: g.Enabled,
|
||||||
|
Primary: g.Primary,
|
||||||
|
Domains: make([]string, len(g.Domains)),
|
||||||
|
SearchDomainsEnabled: g.SearchDomainsEnabled,
|
||||||
|
}
|
||||||
|
|
||||||
|
copy(nsGroup.NameServers, g.NameServers)
|
||||||
|
copy(nsGroup.Groups, g.Groups)
|
||||||
|
copy(nsGroup.Domains, g.Domains)
|
||||||
|
|
||||||
|
return nsGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsEqual compares one nameserver group with the other
|
||||||
|
func (g *DefaultNameServerGroup) IsEqual(other *DefaultNameServerGroup) bool {
|
||||||
|
return other.ID == g.ID &&
|
||||||
|
other.Name == g.Name &&
|
||||||
|
other.Description == g.Description &&
|
||||||
|
other.Primary == g.Primary &&
|
||||||
|
other.SearchDomainsEnabled == g.SearchDomainsEnabled &&
|
||||||
|
compareNameServerList(g.NameServers, other.NameServers) &&
|
||||||
|
compareGroupsList(g.Groups, other.Groups) &&
|
||||||
|
compareGroupsList(g.Domains, other.Domains)
|
||||||
|
}
|
||||||
7
management/refactor/resources/dns/types/settings.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
type Settings interface {
|
||||||
|
}
|
||||||
|
|
||||||
|
type DefaultSettings struct {
|
||||||
|
}
|
||||||
53
management/refactor/resources/dns/types/simpe_record.go
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SimpleRecord provides a simple DNS record specification for CNAME, A and AAAA records
|
||||||
|
type SimpleRecord struct {
|
||||||
|
// Name domain name
|
||||||
|
Name string
|
||||||
|
// Type of record, 1 for A, 5 for CNAME, 28 for AAAA. see https://pkg.go.dev/github.com/miekg/dns@v1.1.41#pkg-constants
|
||||||
|
Type int
|
||||||
|
// Class dns class, currently use the DefaultClass for all records
|
||||||
|
Class string
|
||||||
|
// TTL time-to-live for the record
|
||||||
|
TTL int
|
||||||
|
// RData is the actual value resolved in a dns query
|
||||||
|
RData string
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a string of the simple record formatted as:
|
||||||
|
// <Name> <TTL> <Class> <Type> <RDATA>
|
||||||
|
func (s SimpleRecord) String() string {
|
||||||
|
fqdn := dns.Fqdn(s.Name)
|
||||||
|
return fmt.Sprintf("%s %d %s %s %s", fqdn, s.TTL, s.Class, dns.Type(s.Type).String(), s.RData)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len returns the length of the RData field, based on its type
|
||||||
|
func (s SimpleRecord) Len() uint16 {
|
||||||
|
emptyString := s.RData == ""
|
||||||
|
switch s.Type {
|
||||||
|
case 1:
|
||||||
|
if emptyString {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return net.IPv4len
|
||||||
|
case 5:
|
||||||
|
if emptyString || s.RData == "." {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return uint16(len(s.RData) + 1)
|
||||||
|
case 28:
|
||||||
|
if emptyString {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return net.IPv6len
|
||||||
|
default:
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
1
management/refactor/resources/groups/api.go
Normal file
@@ -0,0 +1 @@
|
|||||||
|
package groups
|
||||||
1
management/refactor/resources/groups/manager.go
Normal file
@@ -0,0 +1 @@
|
|||||||
|
package groups
|
||||||
1
management/refactor/resources/groups/repository.go
Normal file
@@ -0,0 +1 @@
|
|||||||
|
package groups
|
||||||
23
management/refactor/resources/groups/types/group.go
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
type Group interface {
|
||||||
|
}
|
||||||
|
|
||||||
|
type DefaultGroup struct {
|
||||||
|
// ID of the group
|
||||||
|
ID string
|
||||||
|
|
||||||
|
// AccountID is a reference to Account that this object belongs
|
||||||
|
AccountID string `json:"-" gorm:"index"`
|
||||||
|
|
||||||
|
// Name visible in the UI
|
||||||
|
Name string
|
||||||
|
|
||||||
|
// Issued of the group
|
||||||
|
Issued string
|
||||||
|
|
||||||
|
// Peers list of the group
|
||||||
|
Peers []string `gorm:"serializer:json"`
|
||||||
|
|
||||||
|
IntegrationReference IntegrationReference `gorm:"embedded;embeddedPrefix:integration_ref_"`
|
||||||
|
}
|
||||||
1
management/refactor/resources/network/api.go
Normal file
@@ -0,0 +1 @@
|
|||||||
|
package network
|
||||||
19
management/refactor/resources/network/manager.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package network
|
||||||
|
|
||||||
|
import "github.com/netbirdio/netbird/management/refactor/resources/network/types"
|
||||||
|
|
||||||
|
type Manager interface {
|
||||||
|
GetNetwork(accountID string) (types.Network, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type DefaultManager struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDefaultManager() *DefaultManager {
|
||||||
|
return &DefaultManager{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d DefaultManager) GetNetwork(accountID string) (types.Network, error) {
|
||||||
|
// TODO implement me
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
1
management/refactor/resources/network/repository.go
Normal file
@@ -0,0 +1 @@
|
|||||||
|
package network
|
||||||
70
management/refactor/resources/network/types/network.go
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/c-robinson/iplib"
|
||||||
|
"github.com/rs/xid"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// SubnetSize is a size of the subnet of the global network, e.g. 100.77.0.0/16
|
||||||
|
SubnetSize = 16
|
||||||
|
// NetSize is a global network size 100.64.0.0/10
|
||||||
|
NetSize = 10
|
||||||
|
)
|
||||||
|
|
||||||
|
type Network struct {
|
||||||
|
Identifier string `json:"id"`
|
||||||
|
Net net.IPNet `gorm:"serializer:gob"`
|
||||||
|
Dns string
|
||||||
|
// Serial is an ID that increments by 1 when any change to the network happened (e.g. new peer has been added).
|
||||||
|
// Used to synchronize state to the client apps.
|
||||||
|
Serial uint64
|
||||||
|
|
||||||
|
mu sync.Mutex `json:"-" gorm:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewNetwork creates a new Network initializing it with a Serial=0
|
||||||
|
// It takes a random /16 subnet from 100.64.0.0/10 (64 different subnets)
|
||||||
|
func NewNetwork() *Network {
|
||||||
|
|
||||||
|
n := iplib.NewNet4(net.ParseIP("100.64.0.0"), NetSize)
|
||||||
|
sub, _ := n.Subnet(SubnetSize)
|
||||||
|
|
||||||
|
s := rand.NewSource(time.Now().Unix())
|
||||||
|
r := rand.New(s)
|
||||||
|
intn := r.Intn(len(sub))
|
||||||
|
|
||||||
|
return &Network{
|
||||||
|
Identifier: xid.New().String(),
|
||||||
|
Net: sub[intn].IPNet,
|
||||||
|
Dns: "",
|
||||||
|
Serial: 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IncSerial increments Serial by 1 reflecting that the network state has been changed
|
||||||
|
func (n *Network) IncSerial() {
|
||||||
|
n.mu.Lock()
|
||||||
|
defer n.mu.Unlock()
|
||||||
|
n.Serial++
|
||||||
|
}
|
||||||
|
|
||||||
|
// CurrentSerial returns the Network.Serial of the network (latest state id)
|
||||||
|
func (n *Network) CurrentSerial() uint64 {
|
||||||
|
n.mu.Lock()
|
||||||
|
defer n.mu.Unlock()
|
||||||
|
return n.Serial
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Network) Copy() *Network {
|
||||||
|
return &Network{
|
||||||
|
Identifier: n.Identifier,
|
||||||
|
Net: n.Net,
|
||||||
|
Dns: n.Dns,
|
||||||
|
Serial: n.Serial,
|
||||||
|
}
|
||||||
|
}
|
||||||
18
management/refactor/resources/network/types/network_map.go
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
peerTypes "github.com/netbirdio/netbird/management/refactor/resources/peers/types"
|
||||||
|
policyTypes "github.com/netbirdio/netbird/management/refactor/resources/policies/types"
|
||||||
|
routeTypes "github.com/netbirdio/netbird/management/refactor/resources/routes/types"
|
||||||
|
|
||||||
|
nbdns "github.com/netbirdio/netbird/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
type NetworkMap struct {
|
||||||
|
Peers []*peerTypes.Peer
|
||||||
|
Network *Network
|
||||||
|
Routes []*routeTypes.Route
|
||||||
|
DNSConfig nbdns.Config
|
||||||
|
OfflinePeers []*peerTypes.Peer
|
||||||
|
FirewallRules []*policyTypes.FirewallRule
|
||||||
|
}
|
||||||
317
management/refactor/resources/peers/api.go
Normal file
@@ -0,0 +1,317 @@
|
|||||||
|
package peers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
|
||||||
|
http2 "github.com/netbirdio/netbird/management/refactor/api/http"
|
||||||
|
peerTypes "github.com/netbirdio/netbird/management/refactor/resources/peers/types"
|
||||||
|
"github.com/netbirdio/netbird/management/refactor/store"
|
||||||
|
"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"
|
||||||
|
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
||||||
|
"github.com/netbirdio/netbird/management/server/status"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RegisterPeersEndpoints(manager Manager, router *mux.Router) {
|
||||||
|
peersHandler := NewDefaultPeersHandler(manager, apiHandler.AuthCfg)
|
||||||
|
router.HandleFunc("/peers", peersHandler.GetAllPeers).Methods("GET", "OPTIONS")
|
||||||
|
router.HandleFunc("/peers/{peerId}", peersHandler.HandlePeer).
|
||||||
|
Methods("GET", "PUT", "DELETE", "OPTIONS")
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultPeersHandler is a handler that returns peers of the account
|
||||||
|
type DefaultPeersHandler struct {
|
||||||
|
peersManager Manager
|
||||||
|
store store.Store
|
||||||
|
claimsExtractor *jwtclaims.ClaimsExtractor
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDefaultPeersHandler creates a new PeersHandler HTTP handler
|
||||||
|
func NewDefaultPeersHandler(manager Manager, authCfg AuthCfg) *DefaultPeersHandler {
|
||||||
|
return &DefaultPeersHandler{
|
||||||
|
peersManager: manager,
|
||||||
|
claimsExtractor: jwtclaims.NewClaimsExtractor(
|
||||||
|
jwtclaims.WithAudience(authCfg.Audience),
|
||||||
|
jwtclaims.WithUserIDClaim(authCfg.UserIDClaim),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *DefaultPeersHandler) checkPeerStatus(peer *peerTypes.Peer) (*peerTypes.Peer, error) {
|
||||||
|
peerToReturn := peer.Copy()
|
||||||
|
if peer.Status.Connected {
|
||||||
|
// Although we have online status in store we do not yet have an updated channel so have to show it as disconnected
|
||||||
|
// This may happen after server restart when not all peers are yet connected
|
||||||
|
if !h.accountManager.HasConnectedChannel(peer.ID) {
|
||||||
|
peerToReturn.Status.Connected = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return peerToReturn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *DefaultPeersHandler) getPeer(account *server.Account, peerID, userID string, w http.ResponseWriter) {
|
||||||
|
peer, err := h.peersManager.GetPeerByID(account.Id, peerID, userID)
|
||||||
|
if err != nil {
|
||||||
|
util.WriteError(err, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
peerToReturn, err := h.checkPeerStatus(peer)
|
||||||
|
if err != nil {
|
||||||
|
util.WriteError(err, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dnsDomain := h.accountManager.GetDNSDomain()
|
||||||
|
|
||||||
|
groupsInfo := toGroupsInfo(account.Groups, peer.ID)
|
||||||
|
|
||||||
|
netMap := account.GetPeerNetworkMap(peerID, h.accountManager.GetDNSDomain())
|
||||||
|
accessiblePeers := toAccessiblePeers(netMap, dnsDomain)
|
||||||
|
|
||||||
|
util.WriteJSONObject(w, toSinglePeerResponse(peerToReturn, groupsInfo, dnsDomain, accessiblePeers))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *DefaultPeersHandler) updatePeer(account *server.Account, user *server.User, peerID string, w http.ResponseWriter, r *http.Request) {
|
||||||
|
req := &api.PeerRequest{}
|
||||||
|
err := json.NewDecoder(r.Body).Decode(&req)
|
||||||
|
if err != nil {
|
||||||
|
util.WriteErrorResponse("couldn't parse JSON request", http.StatusBadRequest, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
update := &nbpeer.Peer{ID: peerID, SSHEnabled: req.SshEnabled, Name: req.Name,
|
||||||
|
LoginExpirationEnabled: req.LoginExpirationEnabled}
|
||||||
|
|
||||||
|
if req.ApprovalRequired != nil {
|
||||||
|
update.Status = &nbpeer.PeerStatus{RequiresApproval: *req.ApprovalRequired}
|
||||||
|
}
|
||||||
|
|
||||||
|
peer, err := h.accountManager.UpdatePeer(account.Id, user.Id, update)
|
||||||
|
if err != nil {
|
||||||
|
util.WriteError(err, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dnsDomain := h.accountManager.GetDNSDomain()
|
||||||
|
|
||||||
|
groupMinimumInfo := toGroupsInfo(account.Groups, peer.ID)
|
||||||
|
|
||||||
|
netMap := account.GetPeerNetworkMap(peerID, h.accountManager.GetDNSDomain())
|
||||||
|
accessiblePeers := toAccessiblePeers(netMap, dnsDomain)
|
||||||
|
|
||||||
|
util.WriteJSONObject(w, toSinglePeerResponse(peer, groupMinimumInfo, dnsDomain, accessiblePeers))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *DefaultPeersHandler) deletePeer(accountID, userID string, peerID string, w http.ResponseWriter) {
|
||||||
|
err := h.accountManager.DeletePeer(accountID, peerID, userID)
|
||||||
|
if err != nil {
|
||||||
|
util.WriteError(err, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
util.WriteJSONObject(w, http2.EmptyObject{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandlePeer handles all peer requests for GET, PUT and DELETE operations
|
||||||
|
func (h *DefaultPeersHandler) HandlePeer(w http.ResponseWriter, r *http.Request) {
|
||||||
|
claims := h.claimsExtractor.FromRequestContext(r)
|
||||||
|
account, user, err := h.accountManager.GetAccountFromToken(claims)
|
||||||
|
if err != nil {
|
||||||
|
util.WriteError(err, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
peerID := vars["peerId"]
|
||||||
|
if len(peerID) == 0 {
|
||||||
|
util.WriteError(status.Errorf(status.InvalidArgument, "invalid peer ID"), w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch r.Method {
|
||||||
|
case http.MethodDelete:
|
||||||
|
h.deletePeer(account.Id, user.Id, peerID, w)
|
||||||
|
return
|
||||||
|
case http.MethodPut:
|
||||||
|
h.updatePeer(account, user, peerID, w, r)
|
||||||
|
return
|
||||||
|
case http.MethodGet:
|
||||||
|
h.getPeer(account, peerID, user.Id, w)
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
util.WriteError(status.Errorf(status.NotFound, "unknown METHOD"), w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAllPeers returns a list of all peers associated with a provided account
|
||||||
|
func (h *DefaultPeersHandler) GetAllPeers(w http.ResponseWriter, r *http.Request) {
|
||||||
|
switch r.Method {
|
||||||
|
case http.MethodGet:
|
||||||
|
claims := h.claimsExtractor.FromRequestContext(r)
|
||||||
|
account, user, err := h.accountManager.GetAccountFromToken(claims)
|
||||||
|
if err != nil {
|
||||||
|
util.WriteError(err, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
peers, err := h.accountManager.GetPeers(account.Id, user.Id)
|
||||||
|
if err != nil {
|
||||||
|
util.WriteError(err, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
dnsDomain := h.accountManager.GetDNSDomain()
|
||||||
|
|
||||||
|
respBody := make([]*api.PeerBatch, 0, len(peers))
|
||||||
|
for _, peer := range peers {
|
||||||
|
peerToReturn, err := h.checkPeerStatus(peer)
|
||||||
|
if err != nil {
|
||||||
|
util.WriteError(err, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
groupMinimumInfo := toGroupsInfo(account.Groups, peer.ID)
|
||||||
|
|
||||||
|
accessiblePeerNumbers := h.accessiblePeersNumber(account, peer.ID)
|
||||||
|
|
||||||
|
respBody = append(respBody, toPeerListItemResponse(peerToReturn, groupMinimumInfo, dnsDomain, accessiblePeerNumbers))
|
||||||
|
}
|
||||||
|
util.WriteJSONObject(w, respBody)
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
util.WriteError(status.Errorf(status.NotFound, "unknown METHOD"), w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *DefaultPeersHandler) accessiblePeersNumber(account *server.Account, peerID string) int {
|
||||||
|
netMap := account.GetPeerNetworkMap(peerID, h.accountManager.GetDNSDomain())
|
||||||
|
return len(netMap.Peers) + len(netMap.OfflinePeers)
|
||||||
|
}
|
||||||
|
|
||||||
|
func toAccessiblePeers(netMap *server.NetworkMap, dnsDomain string) []api.AccessiblePeer {
|
||||||
|
accessiblePeers := make([]api.AccessiblePeer, 0, len(netMap.Peers)+len(netMap.OfflinePeers))
|
||||||
|
for _, p := range netMap.Peers {
|
||||||
|
ap := api.AccessiblePeer{
|
||||||
|
Id: p.ID,
|
||||||
|
Name: p.Name,
|
||||||
|
Ip: p.IP.String(),
|
||||||
|
DnsLabel: fqdn(p, dnsDomain),
|
||||||
|
UserId: p.UserID,
|
||||||
|
}
|
||||||
|
accessiblePeers = append(accessiblePeers, ap)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, p := range netMap.OfflinePeers {
|
||||||
|
ap := api.AccessiblePeer{
|
||||||
|
Id: p.ID,
|
||||||
|
Name: p.Name,
|
||||||
|
Ip: p.IP.String(),
|
||||||
|
DnsLabel: fqdn(p, dnsDomain),
|
||||||
|
UserId: p.UserID,
|
||||||
|
}
|
||||||
|
accessiblePeers = append(accessiblePeers, ap)
|
||||||
|
}
|
||||||
|
return accessiblePeers
|
||||||
|
}
|
||||||
|
|
||||||
|
func toGroupsInfo(groups map[string]*server.Group, peerID string) []api.GroupMinimum {
|
||||||
|
var groupsInfo []api.GroupMinimum
|
||||||
|
groupsChecked := make(map[string]struct{})
|
||||||
|
for _, group := range groups {
|
||||||
|
_, ok := groupsChecked[group.ID]
|
||||||
|
if ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
groupsChecked[group.ID] = struct{}{}
|
||||||
|
for _, pk := range group.Peers {
|
||||||
|
if pk == peerID {
|
||||||
|
info := api.GroupMinimum{
|
||||||
|
Id: group.ID,
|
||||||
|
Name: group.Name,
|
||||||
|
PeersCount: len(group.Peers),
|
||||||
|
}
|
||||||
|
groupsInfo = append(groupsInfo, info)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return groupsInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func toSinglePeerResponse(peer *nbpeer.Peer, groupsInfo []api.GroupMinimum, dnsDomain string, accessiblePeer []api.AccessiblePeer) *api.Peer {
|
||||||
|
osVersion := peer.Meta.OSVersion
|
||||||
|
if osVersion == "" {
|
||||||
|
osVersion = peer.Meta.Core
|
||||||
|
}
|
||||||
|
|
||||||
|
return &api.Peer{
|
||||||
|
Id: peer.ID,
|
||||||
|
Name: peer.Name,
|
||||||
|
Ip: peer.IP.String(),
|
||||||
|
ConnectionIp: peer.Location.ConnectionIP.String(),
|
||||||
|
Connected: peer.Status.Connected,
|
||||||
|
LastSeen: peer.Status.LastSeen,
|
||||||
|
Os: fmt.Sprintf("%s %s", peer.Meta.OS, osVersion),
|
||||||
|
KernelVersion: peer.Meta.KernelVersion,
|
||||||
|
GeonameId: int(peer.Location.GeoNameID),
|
||||||
|
Version: peer.Meta.WtVersion,
|
||||||
|
Groups: groupsInfo,
|
||||||
|
SshEnabled: peer.SSHEnabled,
|
||||||
|
Hostname: peer.Meta.Hostname,
|
||||||
|
UserId: peer.UserID,
|
||||||
|
UiVersion: peer.Meta.UIVersion,
|
||||||
|
DnsLabel: fqdn(peer, dnsDomain),
|
||||||
|
LoginExpirationEnabled: peer.LoginExpirationEnabled,
|
||||||
|
LastLogin: peer.LastLogin,
|
||||||
|
LoginExpired: peer.Status.LoginExpired,
|
||||||
|
AccessiblePeers: accessiblePeer,
|
||||||
|
ApprovalRequired: &peer.Status.RequiresApproval,
|
||||||
|
CountryCode: peer.Location.CountryCode,
|
||||||
|
CityName: peer.Location.CityName,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func toPeerListItemResponse(peer *nbpeer.Peer, groupsInfo []api.GroupMinimum, dnsDomain string, accessiblePeersCount int) *api.PeerBatch {
|
||||||
|
osVersion := peer.Meta.OSVersion
|
||||||
|
if osVersion == "" {
|
||||||
|
osVersion = peer.Meta.Core
|
||||||
|
}
|
||||||
|
|
||||||
|
return &api.PeerBatch{
|
||||||
|
Id: peer.ID,
|
||||||
|
Name: peer.Name,
|
||||||
|
Ip: peer.IP.String(),
|
||||||
|
ConnectionIp: peer.Location.ConnectionIP.String(),
|
||||||
|
Connected: peer.Status.Connected,
|
||||||
|
LastSeen: peer.Status.LastSeen,
|
||||||
|
Os: fmt.Sprintf("%s %s", peer.Meta.OS, osVersion),
|
||||||
|
KernelVersion: peer.Meta.KernelVersion,
|
||||||
|
GeonameId: int(peer.Location.GeoNameID),
|
||||||
|
Version: peer.Meta.WtVersion,
|
||||||
|
Groups: groupsInfo,
|
||||||
|
SshEnabled: peer.SSHEnabled,
|
||||||
|
Hostname: peer.Meta.Hostname,
|
||||||
|
UserId: peer.UserID,
|
||||||
|
UiVersion: peer.Meta.UIVersion,
|
||||||
|
DnsLabel: fqdn(peer, dnsDomain),
|
||||||
|
LoginExpirationEnabled: peer.LoginExpirationEnabled,
|
||||||
|
LastLogin: peer.LastLogin,
|
||||||
|
LoginExpired: peer.Status.LoginExpired,
|
||||||
|
AccessiblePeersCount: accessiblePeersCount,
|
||||||
|
ApprovalRequired: &peer.Status.RequiresApproval,
|
||||||
|
CountryCode: peer.Location.CountryCode,
|
||||||
|
CityName: peer.Location.CityName,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fqdn(peer *nbpeer.Peer, dnsDomain string) string {
|
||||||
|
fqdn := peer.FQDN(dnsDomain)
|
||||||
|
if fqdn == "" {
|
||||||
|
return peer.DNSLabel
|
||||||
|
} else {
|
||||||
|
return fqdn
|
||||||
|
}
|
||||||
|
}
|
||||||
51
management/refactor/resources/peers/manager.go
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
package peers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/netbirdio/netbird/management/refactor/resources/peers/types"
|
||||||
|
"github.com/netbirdio/netbird/management/refactor/resources/settings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Manager interface {
|
||||||
|
GetPeerByPubKey(pubKey string) (types.Peer, error)
|
||||||
|
GetPeerByID(id string) (types.Peer, error)
|
||||||
|
GetNetworkPeerByID(id string) (types.Peer, error)
|
||||||
|
GetNetworkPeersInAccount(id string) ([]types.Peer, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type DefaultManager struct {
|
||||||
|
repository Repository
|
||||||
|
settingsManager settings.Manager
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDefaultManager(repository Repository, settingsManager settings.Manager) *DefaultManager {
|
||||||
|
return &DefaultManager{
|
||||||
|
repository: repository,
|
||||||
|
settingsManager: settingsManager,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dm *DefaultManager) GetNetworkPeerByID(id string) (types.Peer, error) {
|
||||||
|
return dm.repository.FindPeerByID(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dm *DefaultManager) GetNetworkPeersInAccount(accountId string) ([]types.Peer, error) {
|
||||||
|
defaultPeers, err := dm.repository.FindAllPeersInAccount(accountId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
peers := make([]types.Peer, len(defaultPeers))
|
||||||
|
for _, dp := range defaultPeers {
|
||||||
|
peers = append(peers, dp)
|
||||||
|
}
|
||||||
|
|
||||||
|
return peers, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dm *DefaultManager) GetPeerByPubKey(pubKey string) (types.Peer, error) {
|
||||||
|
return dm.repository.FindPeerByPubKey(pubKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dm *DefaultManager) GetPeerByID(id string) (types.Peer, error) {
|
||||||
|
return dm.repository.FindPeerByID(id)
|
||||||
|
}
|
||||||
10
management/refactor/resources/peers/repository.go
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
package peers
|
||||||
|
|
||||||
|
import "github.com/netbirdio/netbird/management/refactor/resources/peers/types"
|
||||||
|
|
||||||
|
type Repository interface {
|
||||||
|
FindPeerByPubKey(pubKey string) (types.Peer, error)
|
||||||
|
FindPeerByID(id string) (types.Peer, error)
|
||||||
|
FindAllPeersInAccount(id string) ([]types.Peer, error)
|
||||||
|
UpdatePeer(peer types.Peer) error
|
||||||
|
}
|
||||||
261
management/refactor/resources/peers/types/peer.go
Normal file
@@ -0,0 +1,261 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Peer interface {
|
||||||
|
GetID() string
|
||||||
|
SetID(string)
|
||||||
|
GetAccountID() string
|
||||||
|
SetAccountID(string)
|
||||||
|
GetKey() string
|
||||||
|
SetKey(string)
|
||||||
|
GetSetupKey() string
|
||||||
|
SetSetupKey(string)
|
||||||
|
GetIP() net.IP
|
||||||
|
SetIP(net.IP)
|
||||||
|
GetName() string
|
||||||
|
SetName(string)
|
||||||
|
GetDNSLabel() string
|
||||||
|
SetDNSLabel(string)
|
||||||
|
GetUserID() string
|
||||||
|
SetUserID(string)
|
||||||
|
GetSSHKey() string
|
||||||
|
SetSSHKey(string)
|
||||||
|
GetSSHEnabled() bool
|
||||||
|
SetSSHEnabled(bool)
|
||||||
|
AddedWithSSOLogin() bool
|
||||||
|
UpdateMetaIfNew(meta PeerSystemMeta) bool
|
||||||
|
MarkLoginExpired(expired bool)
|
||||||
|
FQDN(dnsDomain string) string
|
||||||
|
EventMeta(dnsDomain string) map[string]any
|
||||||
|
LoginExpired(expiresIn time.Duration) (bool, time.Duration)
|
||||||
|
Copy() Peer
|
||||||
|
}
|
||||||
|
|
||||||
|
// Peer represents a machine connected to the network.
|
||||||
|
// The Peer is a WireGuard peer identified by a public key
|
||||||
|
type DefaultPeer struct {
|
||||||
|
// ID is an internal ID of the peer
|
||||||
|
ID string `gorm:"primaryKey"`
|
||||||
|
// AccountID is a reference to Account that this object belongs
|
||||||
|
AccountID string `json:"-" gorm:"index;uniqueIndex:idx_peers_account_id_ip"`
|
||||||
|
// WireGuard public key
|
||||||
|
Key string `gorm:"index"`
|
||||||
|
// A setup key this peer was registered with
|
||||||
|
SetupKey string
|
||||||
|
// IP address of the Peer
|
||||||
|
IP net.IP `gorm:"uniqueIndex:idx_peers_account_id_ip"`
|
||||||
|
// Meta is a Peer system meta data
|
||||||
|
Meta PeerSystemMeta `gorm:"embedded;embeddedPrefix:meta_"`
|
||||||
|
// Name is peer's name (machine name)
|
||||||
|
Name string
|
||||||
|
// DNSLabel 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
|
||||||
|
// Status peer's management connection status
|
||||||
|
Status *PeerStatus `gorm:"embedded;embeddedPrefix:peer_status_"`
|
||||||
|
// The user ID that registered the peer
|
||||||
|
UserID string
|
||||||
|
// SSHKey is a public SSH key of the peer
|
||||||
|
SSHKey string
|
||||||
|
// SSHEnabled indicates whether SSH server is enabled on the peer
|
||||||
|
SSHEnabled bool
|
||||||
|
// LoginExpirationEnabled indicates whether peer's login expiration is enabled and once expired the peer has to re-login.
|
||||||
|
// Works with LastLogin
|
||||||
|
LoginExpirationEnabled bool
|
||||||
|
// LastLogin the time when peer performed last login operation
|
||||||
|
LastLogin time.Time
|
||||||
|
// CreatedAt records the time the peer was created
|
||||||
|
CreatedAt time.Time
|
||||||
|
// Indicate ephemeral peer attribute
|
||||||
|
Ephemeral bool
|
||||||
|
// Geo location based on connection IP
|
||||||
|
Location Location `gorm:"embedded;embeddedPrefix:location_"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Location is a geo location information of a Peer based on public connection IP
|
||||||
|
type Location struct {
|
||||||
|
ConnectionIP net.IP // from grpc peer or reverse proxy headers depends on setup
|
||||||
|
CountryCode string
|
||||||
|
CityName string
|
||||||
|
GeoNameID uint // city level geoname id
|
||||||
|
}
|
||||||
|
|
||||||
|
// PeerLogin used as a data object between the gRPC API and AccountManager on Login request.
|
||||||
|
type PeerLogin struct {
|
||||||
|
// WireGuardPubKey is a peers WireGuard public key
|
||||||
|
WireGuardPubKey string
|
||||||
|
// SSHKey is a peer's ssh key. Can be empty (e.g., old version do not provide it, or this feature is disabled)
|
||||||
|
SSHKey string
|
||||||
|
// Meta is the system information passed by peer, must be always present.
|
||||||
|
Meta PeerSystemMeta
|
||||||
|
// UserID indicates that JWT was used to log in, and it was valid. Can be empty when SetupKey is used or auth is not required.
|
||||||
|
UserID string
|
||||||
|
// AccountID indicates that JWT was used to log in, and it was valid. Can be empty when SetupKey is used or auth is not required.
|
||||||
|
AccountID string
|
||||||
|
// SetupKey references to a server.SetupKey to log in. Can be empty when UserID is used or auth is not required.
|
||||||
|
SetupKey string
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddedWithSSOLogin indicates whether this peer has been added with an SSO login by a user.
|
||||||
|
func (p *DefaultPeer) AddedWithSSOLogin() bool {
|
||||||
|
return p.UserID != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateMetaIfNew updates peer's system metadata if new information is provided
|
||||||
|
// returns true if meta was updated, false otherwise
|
||||||
|
func (p *DefaultPeer) UpdateMetaIfNew(meta PeerSystemMeta) bool {
|
||||||
|
// Avoid overwriting UIVersion if the update was triggered sole by the CLI client
|
||||||
|
if meta.UIVersion == "" {
|
||||||
|
meta.UIVersion = p.Meta.UIVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.Meta.isEqual(meta) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
p.Meta = meta
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkLoginExpired marks peer's status expired or not
|
||||||
|
func (p *DefaultPeer) MarkLoginExpired(expired bool) {
|
||||||
|
newStatus := p.Status.Copy()
|
||||||
|
newStatus.LoginExpired = expired
|
||||||
|
if expired {
|
||||||
|
newStatus.Connected = false
|
||||||
|
}
|
||||||
|
p.Status = newStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoginExpired indicates whether the peer's login has expired or not.
|
||||||
|
// If Peer.LastLogin plus the expiresIn duration has happened already; then login has expired.
|
||||||
|
// Return true if a login has expired, false otherwise, and time left to expiration (negative when expired).
|
||||||
|
// Login expiration can be disabled/enabled on a Peer level via Peer.LoginExpirationEnabled property.
|
||||||
|
// Login expiration can also be disabled/enabled globally on the Account level via Settings.PeerLoginExpirationEnabled.
|
||||||
|
// Only peers added by interactive SSO login can be expired.
|
||||||
|
func (p *DefaultPeer) LoginExpired(expiresIn time.Duration) (bool, time.Duration) {
|
||||||
|
if !p.AddedWithSSOLogin() || !p.LoginExpirationEnabled {
|
||||||
|
return false, 0
|
||||||
|
}
|
||||||
|
expiresAt := p.LastLogin.Add(expiresIn)
|
||||||
|
now := time.Now()
|
||||||
|
timeLeft := expiresAt.Sub(now)
|
||||||
|
return timeLeft <= 0, timeLeft
|
||||||
|
}
|
||||||
|
|
||||||
|
// FQDN returns peers FQDN combined of the peer's DNS label and the system's DNS domain
|
||||||
|
func (p *DefaultPeer) FQDN(dnsDomain string) string {
|
||||||
|
if dnsDomain == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s.%s", p.DNSLabel, dnsDomain)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EventMeta returns activity event meta related to the peer
|
||||||
|
func (p *DefaultPeer) EventMeta(dnsDomain string) map[string]any {
|
||||||
|
return map[string]any{"name": p.Name, "fqdn": p.FQDN(dnsDomain), "ip": p.IP, "created_at": p.CreatedAt}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *DefaultPeer) GetID() string {
|
||||||
|
// TODO implement me
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *DefaultPeer) SetID(s string) {
|
||||||
|
// TODO implement me
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *DefaultPeer) GetAccountID() string {
|
||||||
|
// TODO implement me
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *DefaultPeer) SetAccountID(s string) {
|
||||||
|
// TODO implement me
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *DefaultPeer) GetKey() string {
|
||||||
|
// TODO implement me
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *DefaultPeer) SetKey(s string) {
|
||||||
|
// TODO implement me
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *DefaultPeer) GetSetupKey() string {
|
||||||
|
// TODO implement me
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *DefaultPeer) SetSetupKey(s string) {
|
||||||
|
// TODO implement me
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *DefaultPeer) GetIP() net.IP {
|
||||||
|
// TODO implement me
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *DefaultPeer) SetIP(ip net.IP) {
|
||||||
|
// TODO implement me
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *DefaultPeer) GetName() string {
|
||||||
|
// TODO implement me
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *DefaultPeer) SetName(s string) {
|
||||||
|
// TODO implement me
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *DefaultPeer) GetDNSLabel() string {
|
||||||
|
// TODO implement me
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *DefaultPeer) SetDNSLabel(s string) {
|
||||||
|
// TODO implement me
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *DefaultPeer) GetUserID() string {
|
||||||
|
// TODO implement me
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *DefaultPeer) SetUserID(s string) {
|
||||||
|
// TODO implement me
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *DefaultPeer) GetSSHKey() string {
|
||||||
|
// TODO implement me
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *DefaultPeer) SetSSHKey(s string) {
|
||||||
|
// TODO implement me
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *DefaultPeer) GetSSHEnabled() bool {
|
||||||
|
// TODO implement me
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *DefaultPeer) SetSSHEnabled(b bool) {
|
||||||
|
// TODO implement me
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
24
management/refactor/resources/peers/types/peer_status.go
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// Copy PeerStatus
|
||||||
|
func (p *PeerStatus) Copy() *PeerStatus {
|
||||||
|
return &PeerStatus{
|
||||||
|
LastSeen: p.LastSeen,
|
||||||
|
Connected: p.Connected,
|
||||||
|
LoginExpired: p.LoginExpired,
|
||||||
|
RequiresApproval: p.RequiresApproval,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type PeerStatus struct { //nolint:revive
|
||||||
|
// LastSeen is the last time peer was connected to the management service
|
||||||
|
LastSeen time.Time
|
||||||
|
// Connected indicates whether peer is connected to the management service or not
|
||||||
|
Connected bool
|
||||||
|
// LoginExpired
|
||||||
|
LoginExpired bool
|
||||||
|
// RequiresApproval indicates whether peer requires approval or not
|
||||||
|
RequiresApproval bool
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import "net/netip"
|
||||||
|
|
||||||
|
// NetworkAddress is the IP address with network and MAC address of a network interface
|
||||||
|
type NetworkAddress struct {
|
||||||
|
NetIP netip.Prefix `gorm:"serializer:json"`
|
||||||
|
Mac string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Environment is a system environment information
|
||||||
|
type Environment struct {
|
||||||
|
Cloud string
|
||||||
|
Platform string
|
||||||
|
}
|
||||||
|
|
||||||
|
// PeerSystemMeta is a metadata of a Peer machine system
|
||||||
|
type PeerSystemMeta struct { //nolint:revive
|
||||||
|
Hostname string
|
||||||
|
GoOS string
|
||||||
|
Kernel string
|
||||||
|
Core string
|
||||||
|
Platform string
|
||||||
|
OS string
|
||||||
|
OSVersion string
|
||||||
|
WtVersion string
|
||||||
|
UIVersion string
|
||||||
|
KernelVersion string
|
||||||
|
NetworkAddresses []NetworkAddress `gorm:"serializer:json"`
|
||||||
|
SystemSerialNumber string
|
||||||
|
SystemProductName string
|
||||||
|
SystemManufacturer string
|
||||||
|
Environment Environment `gorm:"serializer:json"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p PeerSystemMeta) isEqual(other PeerSystemMeta) bool {
|
||||||
|
if len(p.NetworkAddresses) != len(other.NetworkAddresses) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, addr := range p.NetworkAddresses {
|
||||||
|
var found bool
|
||||||
|
for _, oAddr := range other.NetworkAddresses {
|
||||||
|
if addr.Mac == oAddr.Mac && addr.NetIP == oAddr.NetIP {
|
||||||
|
found = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.Hostname == other.Hostname &&
|
||||||
|
p.GoOS == other.GoOS &&
|
||||||
|
p.Kernel == other.Kernel &&
|
||||||
|
p.KernelVersion == other.KernelVersion &&
|
||||||
|
p.Core == other.Core &&
|
||||||
|
p.Platform == other.Platform &&
|
||||||
|
p.OS == other.OS &&
|
||||||
|
p.OSVersion == other.OSVersion &&
|
||||||
|
p.WtVersion == other.WtVersion &&
|
||||||
|
p.UIVersion == other.UIVersion &&
|
||||||
|
p.SystemSerialNumber == other.SystemSerialNumber &&
|
||||||
|
p.SystemProductName == other.SystemProductName &&
|
||||||
|
p.SystemManufacturer == other.SystemManufacturer &&
|
||||||
|
p.Environment.Cloud == other.Environment.Cloud &&
|
||||||
|
p.Environment.Platform == other.Environment.Platform
|
||||||
|
}
|
||||||
1
management/refactor/resources/policies/api.go
Normal file
@@ -0,0 +1 @@
|
|||||||
|
package policies
|
||||||
33
management/refactor/resources/policies/manager.go
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
package policies
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/netbirdio/netbird/management/refactor/resources/peers"
|
||||||
|
"github.com/netbirdio/netbird/management/refactor/resources/peers/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Manager interface {
|
||||||
|
GetAccessiblePeersAndFirewallRules(peerID string) (peers []types.Peer, firewallRules []*FirewallRule)
|
||||||
|
}
|
||||||
|
|
||||||
|
type DefaultManager struct {
|
||||||
|
repository Repository
|
||||||
|
peerManager peers.Manager
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDefaultManager(repository Repository, peerManager peers.Manager) *DefaultManager {
|
||||||
|
return &DefaultManager{
|
||||||
|
repository: repository,
|
||||||
|
peerManager: peerManager,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dm *DefaultManager) GetAccessiblePeersAndFirewallRules(peerID string) (peers []types.Peer, firewallRules []*FirewallRule) {
|
||||||
|
peer, err := dm.peerManager.GetPeerByID(peerID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
peers, err = dm.peerManager.GetNetworkPeersInAccount(peer.GetAccountID())
|
||||||
|
|
||||||
|
return peers, nil
|
||||||
|
}
|
||||||
1
management/refactor/resources/policies/posture/api.go
Normal file
@@ -0,0 +1 @@
|
|||||||
|
package posture
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
package posture
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
package posture
|
||||||
4
management/refactor/resources/policies/repository.go
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
package policies
|
||||||
|
|
||||||
|
type Repository interface {
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
// FirewallRule is a rule of the firewall.
|
||||||
|
type FirewallRule struct {
|
||||||
|
// PeerIP of the peer
|
||||||
|
PeerIP string
|
||||||
|
|
||||||
|
// Direction of the traffic
|
||||||
|
Direction int
|
||||||
|
|
||||||
|
// Action of the traffic
|
||||||
|
Action string
|
||||||
|
|
||||||
|
// Protocol of the traffic
|
||||||
|
Protocol string
|
||||||
|
|
||||||
|
// Port of the traffic
|
||||||
|
Port string
|
||||||
|
}
|
||||||
13
management/refactor/resources/policies/types/policy.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
type Policy interface {
|
||||||
|
GetID() string
|
||||||
|
}
|
||||||
|
|
||||||
|
type DefaultPolicy struct {
|
||||||
|
ID string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dp *DefaultPolicy) GetID() string {
|
||||||
|
return dp.ID
|
||||||
|
}
|
||||||
13
management/refactor/resources/policies/types/policy_rule.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
type PolicyRule interface {
|
||||||
|
GetID() string
|
||||||
|
}
|
||||||
|
|
||||||
|
type DefaultPolicyRule struct {
|
||||||
|
ID string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dpr *DefaultPolicyRule) GetID() string {
|
||||||
|
return dpr.ID
|
||||||
|
}
|
||||||
1
management/refactor/resources/routes/api.go
Normal file
@@ -0,0 +1 @@
|
|||||||
|
package routes
|
||||||
100
management/refactor/resources/routes/manager.go
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
package routes
|
||||||
|
|
||||||
|
import (
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/management/refactor/resources/peers"
|
||||||
|
"github.com/netbirdio/netbird/management/refactor/resources/peers/types"
|
||||||
|
routeTypes "github.com/netbirdio/netbird/management/refactor/resources/routes/types"
|
||||||
|
"github.com/netbirdio/netbird/route"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Manager interface {
|
||||||
|
GetRoutesToSync(peerID string, peersToConnect []*types.Peer, accountID string) []*routeTypes.Route
|
||||||
|
}
|
||||||
|
|
||||||
|
type DefaultManager struct {
|
||||||
|
repository Repository
|
||||||
|
peersManager peers.Manager
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDefaultManager(repository Repository, peersManager peers.Manager) *DefaultManager {
|
||||||
|
return &DefaultManager{
|
||||||
|
repository: repository,
|
||||||
|
peersManager: peersManager,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d DefaultManager) GetRoutesToSync(peerID string, peersToConnect []*types.Peer) []*routeTypes.Route {
|
||||||
|
routes, peerDisabledRoutes := d.getRoutingPeerRoutes(peerID)
|
||||||
|
peerRoutesMembership := make(lookupMap)
|
||||||
|
for _, r := range append(routes, peerDisabledRoutes...) {
|
||||||
|
peerRoutesMembership[route.GetHAUniqueID(r)] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
groupListMap := a.getPeerGroups(peerID)
|
||||||
|
for _, peer := range aclPeers {
|
||||||
|
activeRoutes, _ := a.getRoutingPeerRoutes(peer.ID)
|
||||||
|
groupFilteredRoutes := a.filterRoutesByGroups(activeRoutes, groupListMap)
|
||||||
|
filteredRoutes := a.filterRoutesFromPeersOfSameHAGroup(groupFilteredRoutes, peerRoutesMembership)
|
||||||
|
routes = append(routes, filteredRoutes...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return routes
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d DefaultManager) getRoutingPeerRoutes(accountID, peerID string) (enabledRoutes []routeTypes.Route, disabledRoutes []routeTypes.Route) {
|
||||||
|
peer, err := d.peersManager.GetPeerByID(peerID)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("peer %s that doesn't exist under account %s", peerID, accountID)
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// currently we support only linux routing peers
|
||||||
|
if peer.Meta.GoOS != "linux" {
|
||||||
|
return enabledRoutes, disabledRoutes
|
||||||
|
}
|
||||||
|
|
||||||
|
seenRoute := make(map[string]struct{})
|
||||||
|
|
||||||
|
takeRoute := func(r routeTypes.Route, id string) {
|
||||||
|
if _, ok := seenRoute[r.GetID()]; ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
seenRoute[r.GetID()] = struct{}{}
|
||||||
|
|
||||||
|
if r.IsEnabled() {
|
||||||
|
r.SetPeer(peer.GetKey())
|
||||||
|
enabledRoutes = append(enabledRoutes, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
disabledRoutes = append(disabledRoutes, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, r := range a.Routes {
|
||||||
|
for _, groupID := range r.PeerGroups {
|
||||||
|
group := a.GetGroup(groupID)
|
||||||
|
if group == nil {
|
||||||
|
log.Errorf("route %s has peers group %s that doesn't exist under account %s", r.ID, groupID, a.Id)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, id := range group.Peers {
|
||||||
|
if id != peerID {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
newPeerRoute := r.Copy()
|
||||||
|
newPeerRoute.Peer = id
|
||||||
|
newPeerRoute.PeerGroups = nil
|
||||||
|
newPeerRoute.ID = r.ID + ":" + id // we have to provide unique route id when distribute network map
|
||||||
|
takeRoute(newPeerRoute, id)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if r.Peer == peerID {
|
||||||
|
takeRoute(r.Copy(), peerID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return enabledRoutes, disabledRoutes
|
||||||
|
}
|
||||||
4
management/refactor/resources/routes/repository.go
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
package routes
|
||||||
|
|
||||||
|
type Repository interface {
|
||||||
|
}
|
||||||
54
management/refactor/resources/routes/types/route.go
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import "net/netip"
|
||||||
|
|
||||||
|
const (
|
||||||
|
// InvalidNetwork invalid network type
|
||||||
|
InvalidNetwork NetworkType = iota
|
||||||
|
// IPv4Network IPv4 network type
|
||||||
|
IPv4Network
|
||||||
|
// IPv6Network IPv6 network type
|
||||||
|
IPv6Network
|
||||||
|
)
|
||||||
|
|
||||||
|
// NetworkType route network type
|
||||||
|
type NetworkType int
|
||||||
|
|
||||||
|
type Route interface {
|
||||||
|
GetID() string
|
||||||
|
IsEnabled() bool
|
||||||
|
GetPeer() string
|
||||||
|
SetPeer(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
type DefaultRoute struct {
|
||||||
|
ID string `gorm:"primaryKey"`
|
||||||
|
// AccountID is a reference to Account that this object belongs
|
||||||
|
AccountID string `gorm:"index"`
|
||||||
|
Network netip.Prefix `gorm:"serializer:gob"`
|
||||||
|
NetID string
|
||||||
|
Description string
|
||||||
|
Peer string
|
||||||
|
PeerGroups []string `gorm:"serializer:gob"`
|
||||||
|
NetworkType NetworkType
|
||||||
|
Masquerade bool
|
||||||
|
Metric int
|
||||||
|
Enabled bool
|
||||||
|
Groups []string `gorm:"serializer:json"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *DefaultRoute) GetID() string {
|
||||||
|
return r.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *DefaultRoute) IsEnabled() bool {
|
||||||
|
return r.Enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *DefaultRoute) GetPeer() string {
|
||||||
|
return r.Peer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *DefaultRoute) SetPeer(peer string) {
|
||||||
|
r.Peer = peer
|
||||||
|
}
|
||||||
1
management/refactor/resources/settings/api.go
Normal file
@@ -0,0 +1 @@
|
|||||||
|
package settings
|
||||||
21
management/refactor/resources/settings/manager.go
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
package settings
|
||||||
|
|
||||||
|
import "github.com/netbirdio/netbird/management/refactor/resources/settings/types"
|
||||||
|
|
||||||
|
type Manager interface {
|
||||||
|
GetSettings(accountID string) (types.Settings, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type DefaultManager struct {
|
||||||
|
repository Repository
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDefaultManager(repository Repository) *DefaultManager {
|
||||||
|
return &DefaultManager{
|
||||||
|
repository: repository,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dm *DefaultManager) GetSettings(accountID string) (types.Settings, error) {
|
||||||
|
return dm.repository.FindSettings(accountID)
|
||||||
|
}
|
||||||
7
management/refactor/resources/settings/repository.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package settings
|
||||||
|
|
||||||
|
import "github.com/netbirdio/netbird/management/refactor/resources/settings/types"
|
||||||
|
|
||||||
|
type Repository interface {
|
||||||
|
FindSettings(accountID string) (types.Settings, error)
|
||||||
|
}
|
||||||
34
management/refactor/resources/settings/types/settings.go
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type Settings interface {
|
||||||
|
GetLicense() string
|
||||||
|
GetPeerLoginExpiration() time.Duration
|
||||||
|
SetPeerLoginExpiration(duration time.Duration)
|
||||||
|
GetPeerLoginExpirationEnabled() bool
|
||||||
|
SetPeerLoginExpirationEnabled(bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
type DefaultSettings struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DefaultSettings) GetLicense() string {
|
||||||
|
return "selfhosted"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DefaultSettings) GetPeerLoginExpiration() time.Duration {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DefaultSettings) SetPeerLoginExpiration(duration time.Duration) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DefaultSettings) GetPeerLoginExpirationEnabled() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DefaultSettings) SetPeerLoginExpirationEnabled(bool) {
|
||||||
|
|
||||||
|
}
|
||||||
1
management/refactor/resources/setup_keys/api.go
Normal file
@@ -0,0 +1 @@
|
|||||||
|
package setup_keys
|
||||||
1
management/refactor/resources/setup_keys/manager.go
Normal file
@@ -0,0 +1 @@
|
|||||||
|
package setup_keys
|
||||||
1
management/refactor/resources/setup_keys/repository.go
Normal file
@@ -0,0 +1 @@
|
|||||||
|
package setup_keys
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
type SetupKey interface {
|
||||||
|
}
|
||||||
|
|
||||||
|
type DefaultSetupKey struct {
|
||||||
|
}
|
||||||
1
management/refactor/resources/users/api.go
Normal file
@@ -0,0 +1 @@
|
|||||||
|
package users
|
||||||
27
management/refactor/resources/users/manager.go
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
package users
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/netbirdio/netbird/management/refactor/resources/peers"
|
||||||
|
"github.com/netbirdio/netbird/management/refactor/resources/users/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Manager interface {
|
||||||
|
GetUser(id string) (types.User, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type DefaultManager struct {
|
||||||
|
repository Repository
|
||||||
|
peerManager peers.Manager
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDefaultManager(repository Repository, peerManager peers.Manager) *DefaultManager {
|
||||||
|
return &DefaultManager{
|
||||||
|
repository: repository,
|
||||||
|
peerManager: peerManager,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d DefaultManager) GetUser(id string) (types.User, error) {
|
||||||
|
// TODO implement me
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
package personal_access_tokens
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
package personal_access_tokens
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
package personal_access_tokens
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
package types
|
||||||
4
management/refactor/resources/users/repository.go
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
package users
|
||||||
|
|
||||||
|
type Repository interface {
|
||||||
|
}
|
||||||
35
management/refactor/resources/users/types/user.go
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// UserRole is the role of a User
|
||||||
|
type UserRole string
|
||||||
|
|
||||||
|
type User interface {
|
||||||
|
IsBlocked() bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// User represents a user of the system
|
||||||
|
type DefaultUser struct {
|
||||||
|
Id string `gorm:"primaryKey"`
|
||||||
|
// AccountID is a reference to Account that this object belongs
|
||||||
|
AccountID string `json:"-" gorm:"index"`
|
||||||
|
Role UserRole
|
||||||
|
IsServiceUser bool
|
||||||
|
// NonDeletable indicates whether the service user can be deleted
|
||||||
|
NonDeletable bool
|
||||||
|
// ServiceUserName is only set if IsServiceUser is true
|
||||||
|
ServiceUserName string
|
||||||
|
// AutoGroups is a list of Group IDs to auto-assign to peers registered by this user
|
||||||
|
AutoGroups []string `gorm:"serializer:json"`
|
||||||
|
// Blocked indicates whether the user is blocked. Blocked users can't use the system.
|
||||||
|
Blocked bool
|
||||||
|
// LastLogin is the last time the user logged in to IdP
|
||||||
|
LastLogin time.Time
|
||||||
|
// Issued of the user
|
||||||
|
Issued string `gorm:"default:api"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *DefaultUser) IsBlocked() bool {
|
||||||
|
return u.Blocked
|
||||||
|
}
|
||||||
50
management/refactor/store/account.go
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
package store
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
dnsTypes "github.com/netbirdio/netbird/management/refactor/resources/dns/types"
|
||||||
|
groupTypes "github.com/netbirdio/netbird/management/refactor/resources/groups/types"
|
||||||
|
networkTypes "github.com/netbirdio/netbird/management/refactor/resources/network/types"
|
||||||
|
peerTypes "github.com/netbirdio/netbird/management/refactor/resources/peers/types"
|
||||||
|
policyTypes "github.com/netbirdio/netbird/management/refactor/resources/policies/types"
|
||||||
|
routeTypes "github.com/netbirdio/netbird/management/refactor/resources/routes/types"
|
||||||
|
settingsTypes "github.com/netbirdio/netbird/management/refactor/resources/settings/types"
|
||||||
|
setupKeyTypes "github.com/netbirdio/netbird/management/refactor/resources/setup_keys/types"
|
||||||
|
userTypes "github.com/netbirdio/netbird/management/refactor/resources/users/types"
|
||||||
|
"github.com/netbirdio/netbird/management/server/posture"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Account represents a unique account of the system
|
||||||
|
type DefaultAccount struct {
|
||||||
|
// we have to name column to aid as it collides with Network.Id when work with associations
|
||||||
|
Id string `gorm:"primaryKey"`
|
||||||
|
|
||||||
|
// User.Id it was created by
|
||||||
|
CreatedBy string
|
||||||
|
CreatedAt time.Time
|
||||||
|
Domain string `gorm:"index"`
|
||||||
|
DomainCategory string
|
||||||
|
IsDomainPrimaryAccount bool
|
||||||
|
SetupKeys map[string]*setupKeyTypes.DefaultSetupKey `gorm:"-"`
|
||||||
|
SetupKeysG []setupKeyTypes.DefaultSetupKey `json:"-" gorm:"foreignKey:AccountID;references:id"`
|
||||||
|
Network *networkTypes.Network `gorm:"embedded;embeddedPrefix:network_"`
|
||||||
|
Peers map[string]*peerTypes.DefaultPeer `gorm:"-"`
|
||||||
|
PeersG []peerTypes.DefaultPeer `json:"-" gorm:"foreignKey:AccountID;references:id"`
|
||||||
|
Users map[string]*userTypes.DefaultUser `gorm:"-"`
|
||||||
|
UsersG []userTypes.DefaultUser `json:"-" gorm:"foreignKey:AccountID;references:id"`
|
||||||
|
Groups map[string]*groupTypes.DefaultGroup `gorm:"-"`
|
||||||
|
GroupsG []groupTypes.DefaultGroup `json:"-" gorm:"foreignKey:AccountID;references:id"`
|
||||||
|
Policies []*policyTypes.DefaultPolicy `gorm:"foreignKey:AccountID;references:id"`
|
||||||
|
Routes map[string]*routeTypes.DefaultRoute `gorm:"-"`
|
||||||
|
RoutesG []routeTypes.DefaultRoute `json:"-" gorm:"foreignKey:AccountID;references:id"`
|
||||||
|
NameServerGroups map[string]*dnsTypes.DefaultNameServerGroup `gorm:"-"`
|
||||||
|
NameServerGroupsG []dnsTypes.DefaultNameServerGroup `json:"-" gorm:"foreignKey:AccountID;references:id"`
|
||||||
|
DNSSettings dnsTypes.DefaultSettings `gorm:"embedded;embeddedPrefix:dns_settings_"`
|
||||||
|
PostureChecks []*posture.Checks `gorm:"foreignKey:AccountID;references:id"`
|
||||||
|
// Settings is a dictionary of Account settings
|
||||||
|
Settings *settingsTypes.DefaultSettings `gorm:"embedded;embeddedPrefix:settings_"`
|
||||||
|
// deprecated on store and api level
|
||||||
|
Rules map[string]*Rule `json:"-" gorm:"-"`
|
||||||
|
RulesG []Rule `json:"-" gorm:"-"`
|
||||||
|
}
|
||||||