Compare commits

..

2 Commits

Author SHA1 Message Date
Zoltan Papp
d02b8cbc97 Fix ebpf free call 2023-08-04 10:37:01 +02:00
Zoltan Papp
908a27a047 Add debug logs 2023-08-04 10:36:47 +02:00
74 changed files with 696 additions and 1043 deletions

View File

@@ -1,35 +0,0 @@
name: Test installation
on:
push:
branches:
- main
pull_request:
paths:
- "release_files/install.sh"
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.head_ref || github.actor_id }}
cancel-in-progress: true
jobs:
test-install-script:
strategy:
max-parallel: 2
matrix:
os: [ubuntu-latest, macos-latest]
skip_ui_mode: [true, false]
install_binary: [true, false]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: run install script
env:
SKIP_UI_APP: ${{ matrix.skip_ui_mode }}
USE_BIN_INSTALL: ${{ matrix.install_binary }}
run: |
[ "$SKIP_UI_APP" == "false" ] && export XDG_CURRENT_DESKTOP="none"
cat release_files/install.sh | sh -x
- name: check cli binary
run: command -v netbird

View File

@@ -0,0 +1,60 @@
name: Test installation Darwin
on:
push:
branches:
- main
pull_request:
paths:
- "release_files/install.sh"
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.head_ref || github.actor_id }}
cancel-in-progress: true
jobs:
install-cli-only:
runs-on: macos-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Rename brew package
if: ${{ matrix.check_bin_install }}
run: mv /opt/homebrew/bin/brew /opt/homebrew/bin/brew.bak
- name: Run install script
run: |
sh ./release_files/install.sh
env:
SKIP_UI_APP: true
- name: Run tests
run: |
if ! command -v netbird &> /dev/null; then
echo "Error: netbird is not installed"
exit 1
fi
install-all:
runs-on: macos-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Rename brew package
if: ${{ matrix.check_bin_install }}
run: mv /opt/homebrew/bin/brew /opt/homebrew/bin/brew.bak
- name: Run install script
run: |
sh ./release_files/install.sh
- name: Run tests
run: |
if ! command -v netbird &> /dev/null; then
echo "Error: netbird is not installed"
exit 1
fi
if [[ $(mdfind "kMDItemContentType == 'com.apple.application-bundle' && kMDItemFSName == '*NetBird UI.app'") ]]; then
echo "Error: NetBird UI is not installed"
exit 1
fi

View File

@@ -0,0 +1,38 @@
name: Test installation Linux
on:
push:
branches:
- main
pull_request:
paths:
- "release_files/install.sh"
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.head_ref || github.actor_id }}
cancel-in-progress: true
jobs:
install-cli-only:
runs-on: ubuntu-latest
strategy:
matrix:
check_bin_install: [true, false]
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Rename apt package
if: ${{ matrix.check_bin_install }}
run: |
sudo mv /usr/bin/apt /usr/bin/apt.bak
sudo mv /usr/bin/apt-get /usr/bin/apt-get.bak
- name: Run install script
run: |
sh ./release_files/install.sh
- name: Run tests
run: |
if ! command -v netbird &> /dev/null; then
echo "Error: netbird is not installed"
exit 1
fi

View File

@@ -9,6 +9,7 @@ on:
- 'infrastructure_files/**'
- '.github/workflows/test-infrastructure-files.yml'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.head_ref || github.actor_id }}
cancel-in-progress: true

View File

@@ -1,6 +1,6 @@
<p align="center">
<strong>:hatching_chick: New Release! Self-hosting in under 5 min.</strong>
<a href="https://github.com/netbirdio/netbird#quickstart-with-self-hosted-netbird">
<strong>:hatching_chick: New Release! Peer expiration.</strong>
<a href="https://github.com/netbirdio/netbird/releases">
Learn more
</a>
</p>
@@ -24,7 +24,7 @@
<p align="center">
<strong>
Start using NetBird at <a href="https://netbird.io/pricing">netbird.io</a>
Start using NetBird at <a href="https://app.netbird.io/">app.netbird.io</a>
<br/>
See <a href="https://netbird.io/docs/">Documentation</a>
<br/>
@@ -36,62 +36,47 @@
<br>
**NetBird combines a configuration-free peer-to-peer private network and a centralized access control system in a single platform, making it easy to create secure private networks for your organization or home.**
**NetBird is an open-source VPN management platform built on top of WireGuard® making it easy to create secure private networks for your organization or home.**
**Connect.** NetBird creates a WireGuard-based overlay network that automatically connects your machines over an encrypted tunnel, leaving behind the hassle of opening ports, complex firewall rules, VPN gateways, and so forth.
It requires zero configuration effort leaving behind the hassle of opening ports, complex firewall rules, VPN gateways, and so forth.
**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.
NetBird uses [NAT traversal techniques](https://en.wikipedia.org/wiki/Interactive_Connectivity_Establishment) to automatically create an overlay peer-to-peer network connecting machines regardless of location (home, office, data center, container, cloud, or edge environments), unifying virtual private network management experience.
**Key features:**
- \[x] Automatic IP allocation and network management with a Web UI ([separate repo](https://github.com/netbirdio/dashboard))
- \[x] Automatic WireGuard peer (machine) discovery and configuration.
- \[x] Encrypted peer-to-peer connections without a central VPN gateway.
- \[x] Connection relay fallback in case a peer-to-peer connection is not possible.
- \[x] Desktop client applications for Linux, MacOS, and Windows (systray).
- \[x] Multiuser support - sharing network between multiple users.
- \[x] SSO and MFA support.
- \[x] Multicloud and hybrid-cloud support.
- \[x] Kernel WireGuard usage when possible.
- \[x] Access Controls - groups & rules.
- \[x] Remote SSH access without managing SSH keys.
- \[x] Network Routes.
- \[x] Private DNS.
- \[x] Network Activity Monitoring.
**Coming soon:**
- \[ ] Mobile clients.
### Secure peer-to-peer VPN with SSO and MFA in minutes
https://user-images.githubusercontent.com/700848/197345890-2e2cded5-7b7a-436f-a444-94e80dd24f46.mov
### Key features
**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).
| Connectivity | Management | 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] 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 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] [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] [Access control - groups & rules](https://docs.netbird.io/how-to/manage-network-access) </ul></li> | | <ul><li> - \[ ] 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] [Multiuser support](https://docs.netbird.io/how-to/add-users-to-your-network) </ul></li> | | <ul><li> - \[x] OpenWRT </ul></li> |
| | <ul><li> - \[x] [Activity logging](https://docs.netbird.io/how-to/monitor-system-and-network-activity) </ul></li> | | |
| | <ul><li> - \[x] SSH access management </ul></li> | | |
### Start using NetBird
- Hosted version: [https://app.netbird.io/](https://app.netbird.io/).
- See our documentation for [Quickstart Guide](https://docs.netbird.io/how-to/getting-started).
- If you are looking to self-host NetBird, check our [Self-Hosting Guide](https://docs.netbird.io/selfhosted/selfhosted-guide).
- Step-by-step [Installation Guide](https://docs.netbird.io/how-to/getting-started#installation) for different platforms.
- Web UI [repository](https://github.com/netbirdio/dashboard).
- 5 min [demo video](https://youtu.be/Tu9tPsUWaY0) on YouTube.
### Quickstart with NetBird Cloud
- Download and install NetBird at [https://app.netbird.io/install](https://app.netbird.io/install)
- Follow the steps to sign-up with Google, Microsoft, GitHub or your email address.
- Check NetBird [admin UI](https://app.netbird.io/).
- Add more machines.
### Quickstart with self-hosted NetBird
> This is the quickest way to try self-hosted NetBird. It should take around 5 minutes to get started if you already have a public domain and a VM.
Follow the [Advanced guide with a custom identity provider](https://docs.netbird.io/selfhosted/selfhosted-guide#advanced-guide-with-a-custom-identity-provider) for installations with different IDPs.
**Infrastructure requirements:**
- A Linux VM with at least **1CPU** and **2GB** of memory.
- The VM should be publicly accessible on TCP ports **80** and **443** and UDP ports: **3478**, **49152-65535**.
- **Public domain** name pointing to the VM.
**Software requirements:**
- Docker installed on the VM with the docker compose plugin ([Docker installation guide](https://docs.docker.com/engine/install/)) or docker with docker-compose in version 2 or higher.
- [jq](https://jqlang.github.io/jq/) installed. In most distributions
Usually available in the official repositories and can be installed with `sudo apt install jq` or `sudo yum install jq`
- [curl](https://curl.se/) installed.
Usually available in the official repositories and can be installed with `sudo apt install curl` or `sudo yum install curl`
**Steps**
- Download and run the installation script:
```bash
export NETBIRD_DOMAIN=netbird.example.com; curl -fsSL https://github.com/netbirdio/netbird/releases/latest/download/getting-started-with-zitadel.sh | bash
```
- Once finished, you can manage the resources via `docker-compose`
### A bit on NetBird internals
- Every machine in the network runs [NetBird Agent (or Client)](client/) that manages WireGuard.
- Every agent connects to [Management Service](management/) that holds network state, manages peer IPs, and distributes network updates to agents (peers).
@@ -103,18 +88,18 @@ export NETBIRD_DOMAIN=netbird.example.com; curl -fsSL https://github.com/netbird
[Coturn](https://github.com/coturn/coturn) is the one that has been successfully used for STUN and TURN in NetBird setups.
<p float="left" align="middle">
<img src="https://docs.netbird.io/docs-static/img/architecture/high-level-dia.png" width="700"/>
<img src="https://netbird.io/docs/img/architecture/high-level-dia.png" width="700"/>
</p>
See a complete [architecture overview](https://docs.netbird.io/about-netbird/how-netbird-works#architecture) for details.
### Roadmap
- [Public Roadmap](https://github.com/netbirdio/netbird/projects/2)
### Community projects
- [NetBird on OpenWRT](https://github.com/messense/openwrt-netbird)
- [NetBird installer script](https://github.com/physk/netbird-installer)
**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).
### Support acknowledgement
In November 2022, NetBird joined the [StartUpSecure program](https://www.forschung-it-sicherheit-kommunikationssysteme.de/foerderung/bekanntmachungen/startup-secure) sponsored by The Federal Ministry of Education and Research of The Federal Republic of Germany. Together with [CISPA Helmholtz Center for Information Security](https://cispa.de/en) NetBird brings the security best practices and simplicity to private networking.
@@ -122,7 +107,7 @@ In November 2022, NetBird joined the [StartUpSecure program](https://www.forschu
![CISPA_Logo_BLACK_EN_RZ_RGB (1)](https://user-images.githubusercontent.com/700848/203091324-c6d311a0-22b5-4b05-a288-91cbc6cdcc46.png)
### Testimonials
We use open-source technologies like [WireGuard®](https://www.wireguard.com/), [Pion ICE (WebRTC)](https://github.com/pion/ice), [Coturn](https://github.com/coturn/coturn), and [Rosenpass](https://rosenpass.eu). We very much appreciate the work these guys are doing and we'd greatly appreciate if you could support them in any way (e.g. giving a star or a contribution).
We use open-source technologies like [WireGuard®](https://www.wireguard.com/), [Pion ICE (WebRTC)](https://github.com/pion/ice), and [Coturn](https://github.com/coturn/coturn). We very much appreciate the work these guys are doing and we'd greatly appreciate if you could support them in any way (e.g. giving a star or a contribution).
### Legal
_WireGuard_ and the _WireGuard_ logo are [registered trademarks](https://www.wireguard.com/trademark-policy/) of Jason A. Donenfeld.

View File

@@ -1,5 +1,7 @@
FROM alpine:3
RUN apk add --no-cache ca-certificates iptables ip6tables
FROM gcr.io/distroless/base:debug
ENV NB_FOREGROUND_MODE=true
ENV PATH=/sbin:/usr/sbin:/bin:/usr/bin:/busybox
SHELL ["/busybox/sh","-c"]
RUN sed -i -E 's/(^root:.+)\/sbin\/nologin/\1\/busybox\/sh/g' /etc/passwd
ENTRYPOINT [ "/go/bin/netbird","up"]
COPY netbird /go/bin/netbird

View File

@@ -55,6 +55,7 @@ type Client struct {
ctxCancelLock *sync.Mutex
deviceName string
routeListener routemanager.RouteListener
onHostDnsFn func([]string)
}
// NewClient instantiate a new Client
@@ -96,30 +97,7 @@ func (c *Client) Run(urlOpener URLOpener, dns *DNSList, dnsReadyListener DnsRead
// todo do not throw error in case of cancelled context
ctx = internal.CtxInitState(ctx)
return internal.RunClientMobile(ctx, cfg, c.recorder, c.tunAdapter, c.iFaceDiscover, c.routeListener, dns.items, dnsReadyListener)
}
// RunWithoutLogin we apply this type of run function when the backed has been started without UI (i.e. after reboot).
// In this case make no sense handle registration steps.
func (c *Client) RunWithoutLogin(dns *DNSList, dnsReadyListener DnsReadyListener) error {
cfg, err := internal.UpdateOrCreateConfig(internal.ConfigInput{
ConfigPath: c.cfgFile,
})
if err != nil {
return err
}
c.recorder.UpdateManagementAddress(cfg.ManagementURL.String())
var ctx context.Context
//nolint
ctxWithValues := context.WithValue(context.Background(), system.DeviceNameCtxKey, c.deviceName)
c.ctxCancelLock.Lock()
ctx, c.ctxCancel = context.WithCancel(ctxWithValues)
defer c.ctxCancel()
c.ctxCancelLock.Unlock()
// todo do not throw error in case of cancelled context
ctx = internal.CtxInitState(ctx)
c.onHostDnsFn = func([]string) {}
return internal.RunClientMobile(ctx, cfg, c.recorder, c.tunAdapter, c.iFaceDiscover, c.routeListener, dns.items, dnsReadyListener)
}

View File

@@ -3,21 +3,21 @@ package cmd
import (
"context"
"fmt"
"os"
"runtime"
"github.com/netbirdio/netbird/client/internal/auth"
"strings"
"time"
"github.com/skratchdot/open-golang/open"
"github.com/spf13/cobra"
"google.golang.org/grpc/codes"
gstatus "google.golang.org/grpc/status"
"github.com/netbirdio/netbird/util"
"github.com/spf13/cobra"
"github.com/netbirdio/netbird/client/internal"
"github.com/netbirdio/netbird/client/internal/auth"
"github.com/netbirdio/netbird/client/proto"
"github.com/netbirdio/netbird/client/system"
"github.com/netbirdio/netbird/util"
)
var loginCmd = &cobra.Command{
@@ -191,63 +191,17 @@ func foregroundGetTokenInfo(ctx context.Context, cmd *cobra.Command, config *int
func openURL(cmd *cobra.Command, verificationURIComplete, userCode string) {
var codeMsg string
if userCode != "" && !strings.Contains(verificationURIComplete, userCode) {
codeMsg = fmt.Sprintf("and enter the code %s to authenticate.", userCode)
if userCode != "" {
if !strings.Contains(verificationURIComplete, userCode) {
codeMsg = fmt.Sprintf("and enter the code %s to authenticate.", userCode)
}
}
browserAuthMsg := "Please do the SSO login in your browser. \n" +
err := open.Run(verificationURIComplete)
cmd.Printf("Please do the SSO login in your browser. \n" +
"If your browser didn't open automatically, use this URL to log in:\n\n" +
verificationURIComplete + " " + codeMsg
setupKeyAuthMsg := "\nAlternatively, you may want to use a setup key, see:\n\n" +
"https://docs.netbird.io/how-to/register-machines-using-setup-keys"
authenticateUsingBrowser := func() {
cmd.Println(browserAuthMsg)
if err := open.Run(verificationURIComplete); err != nil {
cmd.Println(setupKeyAuthMsg)
}
}
switch runtime.GOOS {
case "windows", "darwin":
authenticateUsingBrowser()
case "linux":
if isLinuxRunningDesktop() {
authenticateUsingBrowser()
} else {
// If current flow is PKCE, it implies the server is anticipating the redirect to localhost.
// Devices lacking browser support are incompatible with this flow.Therefore,
// these devices will need to resort to setup keys instead.
if isPKCEFlow(verificationURIComplete) {
cmd.Println("Please proceed with setting up this device using setup keys, see:\n\n" +
"https://docs.netbird.io/how-to/register-machines-using-setup-keys")
} else {
cmd.Println(browserAuthMsg)
}
}
" " + verificationURIComplete + " " + codeMsg + " \n\n")
if err != nil {
cmd.Printf("Alternatively, you may want to use a setup key, see:\n\n https://www.netbird.io/docs/overview/setup-keys\n")
}
}
// isLinuxRunningDesktop checks if a Linux OS is running desktop environment.
func isLinuxRunningDesktop() bool {
for _, env := range os.Environ() {
values := strings.Split(env, "=")
if len(values) == 2 {
key, value := values[0], values[1]
if key == "XDG_CURRENT_DESKTOP" && value != "" {
return true
}
}
}
return false
}
// isPKCEFlow determines if the PKCE flow is active or not,
// by checking the existence of redirect_uri inside the verification URL.
func isPKCEFlow(verificationURL string) bool {
if verificationURL == "" {
return false
}
return strings.Contains(verificationURL, "redirect_uri")
}

View File

@@ -120,7 +120,7 @@ func statusFunc(cmd *cobra.Command, args []string) error {
" netbird up \n\n"+
"If you are running a self-hosted version and no SSO provider has been configured in your Management Server,\n"+
"you can use a setup-key:\n\n netbird up --management-url <YOUR_MANAGEMENT_URL> --setup-key <YOUR_SETUP_KEY>\n\n"+
"More info: https://docs.netbird.io/how-to/register-machines-using-setup-keys\n\n",
"More info: https://www.netbird.io/docs/overview/setup-keys\n\n",
resp.GetStatus(),
)
return nil

View File

@@ -59,17 +59,19 @@ func (t TokenInfo) GetTokenToUse() string {
// NewOAuthFlow initializes and returns the appropriate OAuth flow based on the management configuration.
func NewOAuthFlow(ctx context.Context, config *internal.Config) (OAuthFlow, error) {
log.Debug("loading pkce authorization flow info")
log.Debug("getting device authorization flow info")
pkceFlowInfo, err := internal.GetPKCEAuthorizationFlowInfo(ctx, config.PrivateKey, config.ManagementURL)
// Try to initialize the Device Authorization Flow
deviceFlowInfo, err := internal.GetDeviceAuthorizationFlowInfo(ctx, config.PrivateKey, config.ManagementURL)
if err == nil {
return NewPKCEAuthorizationFlow(pkceFlowInfo.ProviderConfig)
return NewDeviceAuthorizationFlow(deviceFlowInfo.ProviderConfig)
}
log.Debugf("loading pkce authorization flow info failed with error: %v", err)
log.Debugf("falling back to device authorization flow info")
log.Debugf("getting device authorization flow info failed with error: %v", err)
log.Debugf("falling back to pkce authorization flow info")
deviceFlowInfo, err := internal.GetDeviceAuthorizationFlowInfo(ctx, config.PrivateKey, config.ManagementURL)
// If Device Authorization Flow failed, try the PKCE Authorization Flow
pkceFlowInfo, err := internal.GetPKCEAuthorizationFlowInfo(ctx, config.PrivateKey, config.ManagementURL)
if err != nil {
s, ok := gstatus.FromError(err)
if ok && s.Code() == codes.NotFound {
@@ -80,9 +82,9 @@ func NewOAuthFlow(ctx context.Context, config *internal.Config) (OAuthFlow, erro
return nil, fmt.Errorf("the management server, %s, does not support SSO providers, "+
"please update your server or use Setup Keys to login", config.ManagementURL)
} else {
return nil, fmt.Errorf("getting device authorization flow info failed with error: %v", err)
return nil, fmt.Errorf("getting pkce authorization flow info failed with error: %v", err)
}
}
return NewDeviceAuthorizationFlow(deviceFlowInfo.ProviderConfig)
return NewPKCEAuthorizationFlow(pkceFlowInfo.ProviderConfig)
}

View File

@@ -179,6 +179,8 @@ func runClient(ctx context.Context, config *Config, statusRecorder *peer.Status,
log.Print("Netbird engine started, my IP is: ", peerConfig.Address)
state.Set(StatusConnected)
statusRecorder.ClientStart()
<-engineCtx.Done()
statusRecorder.ClientTeardown()
@@ -199,7 +201,6 @@ func runClient(ctx context.Context, config *Config, statusRecorder *peer.Status,
return nil
}
statusRecorder.ClientStart()
err = backoff.Retry(operation, backOff)
if err != nil {
log.Debugf("exiting client retry loop due to unrecoverable error: %s", err)

View File

@@ -238,7 +238,7 @@ func (s *DefaultServer) applyConfiguration(update nbdns.Config) error {
hostUpdate := s.currentConfig
if s.service.RuntimePort() != defaultPort && !s.hostManager.supportCustomPort() {
log.Warnf("the DNS manager of this peer doesn't support custom port. Disabling primary DNS setup. " +
"Learn more at: https://docs.netbird.io/how-to/manage-dns-in-your-network#local-resolver")
"Learn more at: https://netbird.io/docs/how-to-guides/nameservers#local-resolver")
hostUpdate.routeAll = false
}

View File

@@ -17,7 +17,6 @@ type notifier struct {
listener Listener
currentClientState bool
lastNotification int
lastNumberOfPeers int
}
func newNotifier() *notifier {
@@ -30,7 +29,6 @@ func (n *notifier) setListener(listener Listener) {
n.serverStateLock.Lock()
n.notifyListener(listener, n.lastNotification)
listener.OnPeersListChanged(n.lastNumberOfPeers)
n.serverStateLock.Unlock()
n.listener = listener
@@ -61,7 +59,7 @@ func (n *notifier) clientStart() {
n.serverStateLock.Lock()
defer n.serverStateLock.Unlock()
n.currentClientState = true
n.lastNotification = stateConnecting
n.lastNotification = stateConnected
n.notify(n.lastNotification)
}
@@ -114,7 +112,7 @@ func (n *notifier) calculateState(managementConn, signalConn bool) int {
return stateConnected
}
if !managementConn && !signalConn && !n.currentClientState {
if !managementConn && !signalConn {
return stateDisconnected
}
@@ -126,7 +124,6 @@ func (n *notifier) calculateState(managementConn, signalConn bool) int {
}
func (n *notifier) peerListChanged(numOfPeers int) {
n.lastNumberOfPeers = numOfPeers
n.listenersLock.Lock()
defer n.listenersLock.Unlock()
if n.listener == nil {

View File

@@ -353,13 +353,9 @@ func (d *Status) onConnectionChanged() {
}
func (d *Status) notifyPeerListChanged() {
d.notifier.peerListChanged(d.numOfPeers())
d.notifier.peerListChanged(len(d.peers) + len(d.offlinePeers))
}
func (d *Status) notifyAddressChanged() {
d.notifier.localAddressChanged(d.localPeer.FQDN, d.localPeer.IP)
}
func (d *Status) numOfPeers() int {
return len(d.peers) + len(d.offlinePeers)
}

View File

@@ -27,19 +27,14 @@ func genKey(format string, input string) string {
}
// NewFirewall if supported, returns an iptables manager, otherwise returns a nftables manager
func NewFirewall(parentCTX context.Context) (firewallManager, error) {
func NewFirewall(parentCTX context.Context) firewallManager {
manager, err := newNFTablesManager(parentCTX)
if err == nil {
log.Debugf("nftables firewall manager will be used")
return manager, nil
return manager
}
fMgr, err := newIptablesManager(parentCTX)
if err != nil {
log.Debugf("failed to initialize iptables for root mgr: %s", err)
return nil, err
}
log.Debugf("iptables firewall manager will be used")
return fMgr, nil
log.Debugf("fallback to iptables firewall manager: %s", err)
return newIptablesManager(parentCTX)
}
func getInPair(pair routerPair) routerPair {

View File

@@ -3,12 +3,24 @@
package routemanager
import (
"context"
"fmt"
)
import "context"
// NewFirewall returns a nil manager
func NewFirewall(context.Context) (firewallManager, error) {
return nil, fmt.Errorf("firewall not supported on this OS")
type unimplementedFirewall struct{}
func (unimplementedFirewall) RestoreOrCreateContainers() error {
return nil
}
func (unimplementedFirewall) InsertRoutingRules(pair routerPair) error {
return nil
}
func (unimplementedFirewall) RemoveRoutingRules(pair routerPair) error {
return nil
}
func (unimplementedFirewall) CleanRoutingRules() {
}
// NewFirewall returns an unimplemented Firewall manager
func NewFirewall(parentCtx context.Context) firewallManager {
return unimplementedFirewall{}
}

View File

@@ -49,12 +49,14 @@ type iptablesManager struct {
mux sync.Mutex
}
func newIptablesManager(parentCtx context.Context) (*iptablesManager, error) {
func newIptablesManager(parentCtx context.Context) *iptablesManager {
ctx, cancel := context.WithCancel(parentCtx)
ipv4Client, err := iptables.NewWithProtocol(iptables.ProtocolIPv4)
if err != nil {
return nil, err
log.Debugf("failed to initialize iptables for ipv4: %s", err)
} else if !isIptablesClientAvailable(ipv4Client) {
return nil, fmt.Errorf("iptables is missing for ipv4")
log.Infof("iptables is missing for ipv4")
ipv4Client = nil
}
ipv6Client, err := iptables.NewWithProtocol(iptables.ProtocolIPv6)
if err != nil {
@@ -64,14 +66,13 @@ func newIptablesManager(parentCtx context.Context) (*iptablesManager, error) {
ipv6Client = nil
}
ctx, cancel := context.WithCancel(parentCtx)
return &iptablesManager{
ctx: ctx,
stop: cancel,
ipv4Client: ipv4Client,
ipv6Client: ipv6Client,
rules: make(map[string]map[string][]string),
}, nil
}
}
// CleanRoutingRules cleans existing iptables resources that we created by the agent
@@ -394,10 +395,6 @@ func (i *iptablesManager) insertRoutingRule(keyFormat, table, chain, jump string
ipVersion = ipv6
}
if iptablesClient == nil {
return fmt.Errorf("unable to insert iptables routing rules. Iptables client is not initialized")
}
ruleKey := genKey(keyFormat, pair.ID)
rule := genRuleSpec(jump, ruleKey, pair.source, pair.destination)
existingRule, found := i.rules[ipVersion][ruleKey]
@@ -462,10 +459,6 @@ func (i *iptablesManager) removeRoutingRule(keyFormat, table, chain string, pair
ipVersion = ipv6
}
if iptablesClient == nil {
return fmt.Errorf("unable to remove iptables routing rules. Iptables client is not initialized")
}
ruleKey := genKey(keyFormat, pair.ID)
existingRule, found := i.rules[ipVersion][ruleKey]
if found {

View File

@@ -16,7 +16,7 @@ func TestIptablesManager_RestoreOrCreateContainers(t *testing.T) {
t.SkipNow()
}
manager, _ := newIptablesManager(context.TODO())
manager := newIptablesManager(context.TODO())
defer manager.CleanRoutingRules()

View File

@@ -27,7 +27,7 @@ type DefaultManager struct {
stop context.CancelFunc
mux sync.Mutex
clientNetworks map[string]*clientNetwork
serverRouter serverRouter
serverRouter *serverRouter
statusRecorder *peer.Status
wgInterface *iface.WGIface
pubKey string
@@ -36,17 +36,13 @@ type DefaultManager struct {
// NewManager returns a new route manager
func NewManager(ctx context.Context, pubKey string, wgInterface *iface.WGIface, statusRecorder *peer.Status, initialRoutes []*route.Route) *DefaultManager {
serverRouter, err := newServerRouter(ctx, wgInterface)
if err != nil {
log.Errorf("server router is not supported: %s", err)
}
mCTX, cancel := context.WithCancel(ctx)
dm := &DefaultManager{
ctx: mCTX,
stop: cancel,
clientNetworks: make(map[string]*clientNetwork),
serverRouter: serverRouter,
serverRouter: newServerRouter(ctx, wgInterface),
statusRecorder: statusRecorder,
wgInterface: wgInterface,
pubKey: pubKey,
@@ -63,9 +59,7 @@ func NewManager(ctx context.Context, pubKey string, wgInterface *iface.WGIface,
// Stop stops the manager watchers and clean firewall rules
func (m *DefaultManager) Stop() {
m.stop()
if m.serverRouter != nil {
m.serverRouter.cleanUp()
}
m.serverRouter.cleanUp()
m.ctx = nil
}
@@ -83,12 +77,9 @@ func (m *DefaultManager) UpdateRoutes(updateSerial uint64, newRoutes []*route.Ro
m.updateClientNetworks(updateSerial, newClientRoutesIDMap)
m.notifier.onNewRoutes(newClientRoutesIDMap)
if m.serverRouter != nil {
err := m.serverRouter.updateRoutes(newServerRoutesMap)
if err != nil {
return err
}
err := m.serverRouter.updateRoutes(newServerRoutesMap)
if err != nil {
return err
}
return nil

View File

@@ -30,6 +30,7 @@ func TestManagerUpdateRoutes(t *testing.T) {
inputInitRoutes []*route.Route
inputRoutes []*route.Route
inputSerial uint64
shouldCheckServerRoutes bool
serverRoutesExpected int
clientNetworkWatchersExpected int
}{
@@ -86,6 +87,7 @@ func TestManagerUpdateRoutes(t *testing.T) {
},
},
inputSerial: 1,
shouldCheckServerRoutes: runtime.GOOS == "linux",
serverRoutesExpected: 2,
clientNetworkWatchersExpected: 0,
},
@@ -114,6 +116,7 @@ func TestManagerUpdateRoutes(t *testing.T) {
},
},
inputSerial: 1,
shouldCheckServerRoutes: runtime.GOOS == "linux",
serverRoutesExpected: 1,
clientNetworkWatchersExpected: 1,
},
@@ -171,6 +174,25 @@ func TestManagerUpdateRoutes(t *testing.T) {
inputSerial: 1,
clientNetworkWatchersExpected: 0,
},
{
name: "No Server Routes Should Be Added To Non Linux",
inputRoutes: []*route.Route{
{
ID: "a",
NetID: "routeA",
Peer: localPeerKey,
Network: netip.MustParsePrefix("1.2.3.4/32"),
NetworkType: route.IPv4Network,
Metric: 9999,
Masquerade: false,
Enabled: true,
},
},
inputSerial: 1,
shouldCheckServerRoutes: runtime.GOOS != "linux",
serverRoutesExpected: 0,
clientNetworkWatchersExpected: 0,
},
{
name: "Remove 1 Client Route",
inputInitRoutes: []*route.Route{
@@ -313,6 +335,7 @@ func TestManagerUpdateRoutes(t *testing.T) {
},
inputRoutes: []*route.Route{},
inputSerial: 1,
shouldCheckServerRoutes: true,
serverRoutesExpected: 0,
clientNetworkWatchersExpected: 0,
},
@@ -361,6 +384,7 @@ func TestManagerUpdateRoutes(t *testing.T) {
},
},
inputSerial: 1,
shouldCheckServerRoutes: runtime.GOOS == "linux",
serverRoutesExpected: 2,
clientNetworkWatchersExpected: 1,
},
@@ -395,9 +419,8 @@ func TestManagerUpdateRoutes(t *testing.T) {
require.Len(t, routeManager.clientNetworks, testCase.clientNetworkWatchersExpected, "client networks size should match")
if runtime.GOOS == "linux" {
sr := routeManager.serverRouter.(*defaultServerRouter)
require.Len(t, sr.routes, testCase.serverRoutesExpected, "server networks size should match")
if testCase.shouldCheckServerRoutes {
require.Len(t, routeManager.serverRouter.routes, testCase.serverRoutesExpected, "server networks size should match")
}
})
}

View File

@@ -1,9 +0,0 @@
package routemanager
import "github.com/netbirdio/netbird/route"
type serverRouter interface {
updateRoutes(map[string]*route.Route) error
removeFromServerNetwork(*route.Route) error
cleanUp()
}

View File

@@ -2,11 +2,20 @@ package routemanager
import (
"context"
"fmt"
"github.com/netbirdio/netbird/iface"
"github.com/netbirdio/netbird/route"
)
func newServerRouter(context.Context, *iface.WGIface) (serverRouter, error) {
return nil, fmt.Errorf("server route not supported on this os")
type serverRouter struct {
}
func newServerRouter(ctx context.Context, wgInterface *iface.WGIface) *serverRouter {
return &serverRouter{}
}
func (r *serverRouter) updateRoutes(routesMap map[string]*route.Route) error {
return nil
}
func (r *serverRouter) cleanUp() {}

View File

@@ -13,7 +13,7 @@ import (
"github.com/netbirdio/netbird/route"
)
type defaultServerRouter struct {
type serverRouter struct {
mux sync.Mutex
ctx context.Context
routes map[string]*route.Route
@@ -21,21 +21,16 @@ type defaultServerRouter struct {
wgInterface *iface.WGIface
}
func newServerRouter(ctx context.Context, wgInterface *iface.WGIface) (serverRouter, error) {
firewall, err := NewFirewall(ctx)
if err != nil {
return nil, err
}
return &defaultServerRouter{
func newServerRouter(ctx context.Context, wgInterface *iface.WGIface) *serverRouter {
return &serverRouter{
ctx: ctx,
routes: make(map[string]*route.Route),
firewall: firewall,
firewall: NewFirewall(ctx),
wgInterface: wgInterface,
}, nil
}
}
func (m *defaultServerRouter) updateRoutes(routesMap map[string]*route.Route) error {
func (m *serverRouter) updateRoutes(routesMap map[string]*route.Route) error {
serverRoutesToRemove := make([]string, 0)
if len(routesMap) > 0 {
@@ -86,7 +81,7 @@ func (m *defaultServerRouter) updateRoutes(routesMap map[string]*route.Route) er
return nil
}
func (m *defaultServerRouter) removeFromServerNetwork(route *route.Route) error {
func (m *serverRouter) removeFromServerNetwork(route *route.Route) error {
select {
case <-m.ctx.Done():
log.Infof("not removing from server network because context is done")
@@ -103,7 +98,7 @@ func (m *defaultServerRouter) removeFromServerNetwork(route *route.Route) error
}
}
func (m *defaultServerRouter) addToServerNetwork(route *route.Route) error {
func (m *serverRouter) addToServerNetwork(route *route.Route) error {
select {
case <-m.ctx.Done():
log.Infof("not adding to server network because context is done")
@@ -120,6 +115,6 @@ func (m *defaultServerRouter) addToServerNetwork(route *route.Route) error {
}
}
func (m *defaultServerRouter) cleanUp() {
func (m *serverRouter) cleanUp() {
m.firewall.CleanRoutingRules()
}

View File

@@ -20,7 +20,7 @@ func InterfaceFilter(disallowList []string) func(string) bool {
for _, s := range disallowList {
if strings.HasPrefix(iFace, s) {
log.Tracef("ignoring interface %s - it is not allowed", iFace)
log.Debugf("ignoring interface %s - it is not allowed", iFace)
return false
}
}

View File

@@ -54,14 +54,14 @@ type bpfSpecs struct {
//
// It can be passed ebpf.CollectionSpec.Assign.
type bpfProgramSpecs struct {
NbWgProxy *ebpf.ProgramSpec `ebpf:"nb_wg_proxy"`
XdpProgFunc *ebpf.ProgramSpec `ebpf:"xdp_prog_func"`
}
// bpfMapSpecs contains maps before they are loaded into the kernel.
//
// It can be passed ebpf.CollectionSpec.Assign.
type bpfMapSpecs struct {
NbWgProxySettingsMap *ebpf.MapSpec `ebpf:"nb_wg_proxy_settings_map"`
XdpPortMap *ebpf.MapSpec `ebpf:"xdp_port_map"`
}
// bpfObjects contains all objects after they have been loaded into the kernel.
@@ -83,12 +83,12 @@ func (o *bpfObjects) Close() error {
//
// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign.
type bpfMaps struct {
NbWgProxySettingsMap *ebpf.Map `ebpf:"nb_wg_proxy_settings_map"`
XdpPortMap *ebpf.Map `ebpf:"xdp_port_map"`
}
func (m *bpfMaps) Close() error {
return _BpfClose(
m.NbWgProxySettingsMap,
m.XdpPortMap,
)
}
@@ -96,12 +96,12 @@ func (m *bpfMaps) Close() error {
//
// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign.
type bpfPrograms struct {
NbWgProxy *ebpf.Program `ebpf:"nb_wg_proxy"`
XdpProgFunc *ebpf.Program `ebpf:"xdp_prog_func"`
}
func (p *bpfPrograms) Close() error {
return _BpfClose(
p.NbWgProxy,
p.XdpProgFunc,
)
}

View File

@@ -54,14 +54,14 @@ type bpfSpecs struct {
//
// It can be passed ebpf.CollectionSpec.Assign.
type bpfProgramSpecs struct {
NbWgProxy *ebpf.ProgramSpec `ebpf:"nb_wg_proxy"`
XdpProgFunc *ebpf.ProgramSpec `ebpf:"xdp_prog_func"`
}
// bpfMapSpecs contains maps before they are loaded into the kernel.
//
// It can be passed ebpf.CollectionSpec.Assign.
type bpfMapSpecs struct {
NbWgProxySettingsMap *ebpf.MapSpec `ebpf:"nb_wg_proxy_settings_map"`
XdpPortMap *ebpf.MapSpec `ebpf:"xdp_port_map"`
}
// bpfObjects contains all objects after they have been loaded into the kernel.
@@ -83,12 +83,12 @@ func (o *bpfObjects) Close() error {
//
// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign.
type bpfMaps struct {
NbWgProxySettingsMap *ebpf.Map `ebpf:"nb_wg_proxy_settings_map"`
XdpPortMap *ebpf.Map `ebpf:"xdp_port_map"`
}
func (m *bpfMaps) Close() error {
return _BpfClose(
m.NbWgProxySettingsMap,
m.XdpPortMap,
)
}
@@ -96,12 +96,12 @@ func (m *bpfMaps) Close() error {
//
// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign.
type bpfPrograms struct {
NbWgProxy *ebpf.Program `ebpf:"nb_wg_proxy"`
XdpProgFunc *ebpf.Program `ebpf:"xdp_prog_func"`
}
func (p *bpfPrograms) Close() error {
return _BpfClose(
p.NbWgProxy,
p.XdpProgFunc,
)
}

View File

@@ -50,22 +50,22 @@ func (l *EBPF) Load(proxyPort, wgPort int) error {
_ = objs.Close()
}()
err = objs.NbWgProxySettingsMap.Put(mapKeyProxyPort, uint16(proxyPort))
err = objs.XdpPortMap.Put(mapKeyProxyPort, uint16(proxyPort))
if err != nil {
return err
}
err = objs.NbWgProxySettingsMap.Put(mapKeyWgPort, uint16(wgPort))
err = objs.XdpPortMap.Put(mapKeyWgPort, uint16(wgPort))
if err != nil {
return err
}
defer func() {
_ = objs.NbWgProxySettingsMap.Close()
_ = objs.XdpPortMap.Close()
}()
l.link, err = link.AttachXDP(link.XDPOptions{
Program: objs.NbWgProxy,
Program: objs.XdpProgFunc,
Interface: ifce.Index,
})
if err != nil {
@@ -75,7 +75,7 @@ func (l *EBPF) Load(proxyPort, wgPort int) error {
return err
}
// Free ebpf program
// Free free ebpf program
func (l *EBPF) Free() error {
if l.link != nil {
return l.link.Close()

View File

@@ -15,7 +15,7 @@
const __u32 map_key_proxy_port = 0;
const __u32 map_key_wg_port = 1;
struct bpf_map_def SEC("maps") nb_wg_proxy_settings_map = {
struct bpf_map_def SEC("maps") xdp_port_map = {
.type = BPF_MAP_TYPE_ARRAY,
.key_size = sizeof(__u32),
.value_size = sizeof(__u16),
@@ -27,14 +27,14 @@ __u16 wg_port = 0;
bool read_port_settings() {
__u16 *value;
value = bpf_map_lookup_elem(&nb_wg_proxy_settings_map, &map_key_proxy_port);
value = bpf_map_lookup_elem(&xdp_port_map, &map_key_proxy_port);
if(!value) {
return false;
}
proxy_port = *value;
value = bpf_map_lookup_elem(&nb_wg_proxy_settings_map, &map_key_wg_port);
value = bpf_map_lookup_elem(&xdp_port_map, &map_key_wg_port);
if(!value) {
return false;
}
@@ -44,7 +44,7 @@ bool read_port_settings() {
}
SEC("xdp")
int nb_wg_proxy(struct xdp_md *ctx) {
int xdp_prog_func(struct xdp_md *ctx) {
if(proxy_port == 0 || wg_port == 0) {
if(!read_port_settings()){
return XDP_PASS;

View File

@@ -156,9 +156,11 @@ func (p *WGEBPFProxy) proxyToRemote() {
p.turnConnMutex.Lock()
conn, ok := p.turnConnStore[uint16(addr.Port)]
size := len(p.turnConnStore)
p.turnConnMutex.Unlock()
if !ok {
log.Errorf("turn conn not found by port: %d", addr.Port)
log.Debugf("conn store size: %d", size)
continue
}

View File

@@ -38,6 +38,7 @@ func (p *WGUserSpaceProxy) AddTurnConn(remoteConn net.Conn) (net.Addr, error) {
log.Errorf("failed dialing to local Wireguard port %s", err)
return nil, err
}
log.Debugf("add turn conn: %s", remoteConn.RemoteAddr())
go p.proxyToRemote()
go p.proxyToLocal()

View File

@@ -130,22 +130,16 @@ func ParseNameServerURL(nsURL string) (NameServer, error) {
// Copy copies a nameserver group object
func (g *NameServerGroup) Copy() *NameServerGroup {
nsGroup := &NameServerGroup{
return &NameServerGroup{
ID: g.ID,
Name: g.Name,
Description: g.Description,
NameServers: make([]NameServer, len(g.NameServers)),
Groups: make([]string, len(g.Groups)),
NameServers: g.NameServers,
Groups: g.Groups,
Enabled: g.Enabled,
Primary: g.Primary,
Domains: make([]string, len(g.Domains)),
Domains: g.Domains,
}
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

2
go.mod
View File

@@ -31,7 +31,7 @@ require (
fyne.io/fyne/v2 v2.1.4
github.com/c-robinson/iplib v1.0.3
github.com/cilium/ebpf v0.10.0
github.com/coreos/go-iptables v0.7.0
github.com/coreos/go-iptables v0.6.0
github.com/creack/pty v1.1.18
github.com/eko/gocache/v3 v3.1.1
github.com/getlantern/systray v1.2.1

4
go.sum
View File

@@ -131,8 +131,8 @@ github.com/containerd/typeurl v1.0.2/go.mod h1:9trJWW2sRlGub4wZJRTW83VtbOLS6hwcD
github.com/coocood/freecache v1.2.1 h1:/v1CqMq45NFH9mp/Pt142reundeBM0dVUD3osQBeu/U=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-iptables v0.7.0 h1:XWM3V+MPRr5/q51NuWSgU0fqMad64Zyxs8ZUoMsamr8=
github.com/coreos/go-iptables v0.7.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
github.com/coreos/go-iptables v0.6.0 h1:is9qnZMPYjLd8LYqmm/qlE+wwEgJIkTYdhV3rfZo4jk=
github.com/coreos/go-iptables v0.6.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=

View File

@@ -395,7 +395,7 @@ check_nb_domain() {
read_nb_domain() {
READ_NETBIRD_DOMAIN=""
echo -n "Enter the domain you want to use for NetBird (e.g. netbird.my-domain.com): " > /dev/stderr
read -r READ_NETBIRD_DOMAIN < /dev/tty
read -r READ_NETBIRD_DOMAIN
if ! check_nb_domain "$READ_NETBIRD_DOMAIN"; then
read_nb_domain
fi

View File

@@ -139,14 +139,10 @@ type DefaultAccountManager struct {
type Settings struct {
// PeerLoginExpirationEnabled globally enables or disables peer login expiration
PeerLoginExpirationEnabled bool
// PeerLoginExpiration is a setting that indicates when peer login expires.
// Applies to all peers that have Peer.LoginExpirationEnabled set to true.
PeerLoginExpiration time.Duration
// GroupsPropagationEnabled allows to propagate auto groups from the user to the peer
GroupsPropagationEnabled bool
// JWTGroupsEnabled allows extract groups from JWT claim, which name defined in the JWTGroupsClaimName
// and add it to account groups.
JWTGroupsEnabled bool
@@ -162,7 +158,6 @@ func (s *Settings) Copy() *Settings {
PeerLoginExpiration: s.PeerLoginExpiration,
JWTGroupsEnabled: s.JWTGroupsEnabled,
JWTGroupsClaimName: s.JWTGroupsClaimName,
GroupsPropagationEnabled: s.GroupsPropagationEnabled,
}
}
@@ -189,15 +184,14 @@ type Account struct {
}
type UserInfo struct {
ID string `json:"id"`
Email string `json:"email"`
Name string `json:"name"`
Role string `json:"role"`
AutoGroups []string `json:"auto_groups"`
Status string `json:"-"`
IsServiceUser bool `json:"is_service_user"`
IsBlocked bool `json:"is_blocked"`
LastLogin time.Time `json:"last_login"`
ID string `json:"id"`
Email string `json:"email"`
Name string `json:"name"`
Role string `json:"role"`
AutoGroups []string `json:"auto_groups"`
Status string `json:"-"`
IsServiceUser bool `json:"is_service_user"`
IsBlocked bool `json:"is_blocked"`
}
// getRoutesToSync returns the enabled routes for the peer ID and the routes
@@ -630,110 +624,26 @@ func (a *Account) GetPeer(peerID string) *Peer {
return a.Peers[peerID]
}
// SetJWTGroups to account and to user autoassigned groups
func (a *Account) SetJWTGroups(userID string, groupsNames []string) bool {
user, ok := a.Users[userID]
if !ok {
return false
// AddJWTGroups to existed groups if they does not exists
func (a *Account) AddJWTGroups(groups []string) (int, error) {
existedGroups := make(map[string]*Group)
for _, g := range a.Groups {
existedGroups[g.Name] = g
}
existedGroupsByName := make(map[string]*Group)
for _, group := range a.Groups {
existedGroupsByName[group.Name] = group
}
// remove JWT groups from the autogroups, to sync them again
removed := 0
jwtAutoGroups := make(map[string]struct{})
for i, id := range user.AutoGroups {
if group, ok := a.Groups[id]; ok && group.Issued == GroupIssuedJWT {
jwtAutoGroups[group.Name] = struct{}{}
user.AutoGroups = append(user.AutoGroups[:i-removed], user.AutoGroups[i-removed+1:]...)
removed++
}
}
// create JWT groups if they doesn't exist
// and all of them to the autogroups
var modified bool
for _, name := range groupsNames {
group, ok := existedGroupsByName[name]
if !ok {
group = &Group{
ID: xid.New().String(),
var count int
for _, name := range groups {
if _, ok := existedGroups[name]; !ok {
id := xid.New().String()
a.Groups[id] = &Group{
ID: id,
Name: name,
Issued: GroupIssuedJWT,
}
a.Groups[group.ID] = group
}
// only JWT groups will be synced
if group.Issued == GroupIssuedJWT {
user.AutoGroups = append(user.AutoGroups, group.ID)
if _, ok := jwtAutoGroups[name]; !ok {
modified = true
}
delete(jwtAutoGroups, name)
count++
}
}
// if not empty it means we removed some groups
if len(jwtAutoGroups) > 0 {
modified = true
}
return modified
}
// UserGroupsAddToPeers adds groups to all peers of user
func (a *Account) UserGroupsAddToPeers(userID string, groups ...string) {
userPeers := make(map[string]struct{})
for pid, peer := range a.Peers {
if peer.UserID == userID {
userPeers[pid] = struct{}{}
}
}
for _, gid := range groups {
group, ok := a.Groups[gid]
if !ok {
continue
}
groupPeers := make(map[string]struct{})
for _, pid := range group.Peers {
groupPeers[pid] = struct{}{}
}
for pid := range userPeers {
groupPeers[pid] = struct{}{}
}
group.Peers = group.Peers[:0]
for pid := range groupPeers {
group.Peers = append(group.Peers, pid)
}
}
}
// UserGroupsRemoveFromPeers removes groups from all peers of user
func (a *Account) UserGroupsRemoveFromPeers(userID string, groups ...string) {
for _, gid := range groups {
group, ok := a.Groups[gid]
if !ok {
continue
}
update := make([]string, 0, len(group.Peers))
for _, pid := range group.Peers {
peer, ok := a.Peers[pid]
if !ok {
continue
}
if peer.UserID != userID {
update = append(update, pid)
}
}
group.Peers = update
}
return count, nil
}
// BuildManager creates a new DefaultAccountManager with a provided Store
@@ -887,7 +797,6 @@ func (am *DefaultAccountManager) peerLoginExpirationJob(accountID string) func()
log.Errorf("failed saving peer status while expiring peer %s", peer.ID)
return account.GetNextPeerExpiration()
}
am.storeEvent(peer.UserID, peer.ID, account.Id, activity.PeerLoginExpired, peer.EventMeta(am.GetDNSDomain()))
}
log.Debugf("discovered %d peers to expire for account %s", len(peerIDs), account.Id)
@@ -1373,58 +1282,21 @@ func (am *DefaultAccountManager) GetAccountFromToken(claims jwtclaims.Authorizat
}
if claim, ok := claims.Raw[account.Settings.JWTGroupsClaimName]; ok {
if slice, ok := claim.([]interface{}); ok {
var groupsNames []string
var groups []string
for _, item := range slice {
if g, ok := item.(string); ok {
groupsNames = append(groupsNames, g)
groups = append(groups, g)
} else {
log.Errorf("JWT claim %q is not a string: %v", account.Settings.JWTGroupsClaimName, item)
}
}
oldGroups := make([]string, len(user.AutoGroups))
copy(oldGroups, user.AutoGroups)
// if groups were added or modified, save the account
if account.SetJWTGroups(claims.UserId, groupsNames) {
if account.Settings.GroupsPropagationEnabled {
if user, err := account.FindUser(claims.UserId); err == nil {
addNewGroups := difference(user.AutoGroups, oldGroups)
removeOldGroups := difference(oldGroups, user.AutoGroups)
account.UserGroupsAddToPeers(claims.UserId, addNewGroups...)
account.UserGroupsRemoveFromPeers(claims.UserId, removeOldGroups...)
account.Network.IncSerial()
if err := am.Store.SaveAccount(account); err != nil {
log.Errorf("failed to save account: %v", err)
} else {
if err := am.updateAccountPeers(account); err != nil {
log.Errorf("failed updating account peers while updating user %s", account.Id)
}
for _, g := range addNewGroups {
if group := account.GetGroup(g); group != nil {
am.storeEvent(user.Id, user.Id, account.Id, activity.GroupAddedToUser,
map[string]any{
"group": group.Name,
"group_id": group.ID,
"is_service_user": user.IsServiceUser,
"user_name": user.ServiceUserName})
}
}
for _, g := range removeOldGroups {
if group := account.GetGroup(g); group != nil {
am.storeEvent(user.Id, user.Id, account.Id, activity.GroupRemovedFromUser,
map[string]any{
"group": group.Name,
"group_id": group.ID,
"is_service_user": user.IsServiceUser,
"user_name": user.ServiceUserName})
}
}
}
}
} else {
if err := am.Store.SaveAccount(account); err != nil {
log.Errorf("failed to save account: %v", err)
}
n, err := account.AddJWTGroups(groups)
if err != nil {
log.Errorf("failed to add JWT groups: %v", err)
}
if n > 0 {
if err := am.Store.SaveAccount(account); err != nil {
log.Errorf("failed to save account: %v", err)
}
}
} else {
@@ -1471,7 +1343,7 @@ func (am *DefaultAccountManager) getAccountWithAuthorizationClaims(claims jwtcla
if _, ok := accountFromID.Users[claims.UserId]; !ok {
return nil, fmt.Errorf("user %s is not part of the account id %s", claims.UserId, claims.AccountId)
}
if accountFromID.DomainCategory == PrivateCategory || claims.DomainCategory != PrivateCategory || accountFromID.Domain != claims.Domain {
if accountFromID.DomainCategory == PrivateCategory || claims.DomainCategory != PrivateCategory {
return accountFromID, nil
}
}

View File

@@ -3,7 +3,6 @@ package server
import (
"crypto/sha256"
b64 "encoding/base64"
"encoding/json"
"fmt"
"net"
"reflect"
@@ -217,6 +216,7 @@ func TestAccount_GetPeerNetworkMap(t *testing.T) {
assert.Len(t, networkMap.Peers, len(testCase.expectedPeers))
assert.Len(t, networkMap.OfflinePeers, len(testCase.expectedOfflinePeers))
}
}
func TestNewAccount(t *testing.T) {
@@ -1349,11 +1349,6 @@ func TestAccount_Copy(t *testing.T) {
Peers: map[string]*Peer{
"peer1": {
Key: "key1",
Status: &PeerStatus{
LastSeen: time.Now(),
Connected: true,
LoginExpired: false,
},
},
},
Users: map[string]*User{
@@ -1376,36 +1371,28 @@ func TestAccount_Copy(t *testing.T) {
},
Groups: map[string]*Group{
"group1": {
ID: "group1",
Peers: []string{"peer1"},
ID: "group1",
},
},
Rules: map[string]*Rule{
"rule1": {
ID: "rule1",
Destination: []string{},
Source: []string{},
ID: "rule1",
},
},
Policies: []*Policy{
{
ID: "policy1",
Enabled: true,
Rules: make([]*PolicyRule, 0),
},
},
Routes: map[string]*route.Route{
"route1": {
ID: "route1",
Groups: []string{"group1"},
ID: "route1",
},
},
NameServerGroups: map[string]*nbdns.NameServerGroup{
"nsGroup1": {
ID: "nsGroup1",
Domains: []string{},
Groups: []string{},
NameServers: []nbdns.NameServer{},
ID: "nsGroup1",
},
},
DNSSettings: &DNSSettings{DisabledManagementGroups: []string{}},
@@ -1416,20 +1403,10 @@ func TestAccount_Copy(t *testing.T) {
t.Fatal(err)
}
accountCopy := account.Copy()
accBytes, err := json.Marshal(account)
if err != nil {
t.Fatal(err)
}
account.Peers["peer1"].Status.Connected = false // we change original object to confirm that copy wont change
accCopyBytes, err := json.Marshal(accountCopy)
if err != nil {
t.Fatal(err)
}
assert.Equal(t, string(accBytes), string(accCopyBytes), "account copy returned a different value than expected")
assert.Equal(t, account, accountCopy, "account copy returned a different value than expected")
}
// hasNilField validates pointers, maps and slices if they are nil
// TODO: make it check nested fields too
func hasNilField(x interface{}) error {
rv := reflect.ValueOf(x)
rv = rv.Elem()
@@ -1954,120 +1931,6 @@ func TestAccount_GetNextPeerExpiration(t *testing.T) {
}
}
func TestAccount_SetJWTGroups(t *testing.T) {
// create a new account
account := &Account{
Peers: map[string]*Peer{
"peer1": {ID: "peer1", Key: "key1", UserID: "user1"},
"peer2": {ID: "peer2", Key: "key2", UserID: "user1"},
"peer3": {ID: "peer3", Key: "key3", UserID: "user1"},
"peer4": {ID: "peer4", Key: "key4", UserID: "user2"},
"peer5": {ID: "peer5", Key: "key5", UserID: "user2"},
},
Groups: map[string]*Group{
"group1": {ID: "group1", Name: "group1", Issued: GroupIssuedAPI, Peers: []string{}},
},
Settings: &Settings{GroupsPropagationEnabled: true},
Users: map[string]*User{
"user1": {Id: "user1"},
"user2": {Id: "user2"},
},
}
t.Run("api group already exists", func(t *testing.T) {
updated := account.SetJWTGroups("user1", []string{"group1"})
assert.False(t, updated, "account should not be updated")
assert.Empty(t, account.Users["user1"].AutoGroups, "auto groups must be empty")
})
t.Run("add jwt group", func(t *testing.T) {
updated := account.SetJWTGroups("user1", []string{"group1", "group2"})
assert.True(t, updated, "account should be updated")
assert.Len(t, account.Groups, 2, "new group should be added")
assert.Len(t, account.Users["user1"].AutoGroups, 1, "new group should be added")
assert.Contains(t, account.Groups, account.Users["user1"].AutoGroups[0], "groups must contain group2 from user groups")
})
t.Run("existed group not update", func(t *testing.T) {
updated := account.SetJWTGroups("user1", []string{"group2"})
assert.False(t, updated, "account should not be updated")
assert.Len(t, account.Groups, 2, "groups count should not be changed")
})
t.Run("add new group", func(t *testing.T) {
updated := account.SetJWTGroups("user2", []string{"group1", "group3"})
assert.True(t, updated, "account should be updated")
assert.Len(t, account.Groups, 3, "new group should be added")
assert.Len(t, account.Users["user2"].AutoGroups, 1, "new group should be added")
assert.Contains(t, account.Groups, account.Users["user2"].AutoGroups[0], "groups must contain group3 from user groups")
})
}
func TestAccount_UserGroupsAddToPeers(t *testing.T) {
account := &Account{
Peers: map[string]*Peer{
"peer1": {ID: "peer1", Key: "key1", UserID: "user1"},
"peer2": {ID: "peer2", Key: "key2", UserID: "user1"},
"peer3": {ID: "peer3", Key: "key3", UserID: "user1"},
"peer4": {ID: "peer4", Key: "key4", UserID: "user2"},
"peer5": {ID: "peer5", Key: "key5", UserID: "user2"},
},
Groups: map[string]*Group{
"group1": {ID: "group1", Name: "group1", Issued: GroupIssuedAPI, Peers: []string{}},
"group2": {ID: "group2", Name: "group2", Issued: GroupIssuedAPI, Peers: []string{}},
"group3": {ID: "group3", Name: "group3", Issued: GroupIssuedAPI, Peers: []string{}},
},
Users: map[string]*User{"user1": {Id: "user1"}, "user2": {Id: "user2"}},
}
t.Run("add groups", func(t *testing.T) {
account.UserGroupsAddToPeers("user1", "group1", "group2")
assert.ElementsMatch(t, account.Groups["group1"].Peers, []string{"peer1", "peer2", "peer3"}, "group1 contains users peers")
assert.ElementsMatch(t, account.Groups["group2"].Peers, []string{"peer1", "peer2", "peer3"}, "group2 contains users peers")
})
t.Run("add same groups", func(t *testing.T) {
account.UserGroupsAddToPeers("user1", "group1", "group2")
assert.Len(t, account.Groups["group1"].Peers, 3, "peers amount in group1 didn't change")
assert.Len(t, account.Groups["group2"].Peers, 3, "peers amount in group2 didn't change")
})
t.Run("add second user peers", func(t *testing.T) {
account.UserGroupsAddToPeers("user2", "group2")
assert.ElementsMatch(t, account.Groups["group2"].Peers,
[]string{"peer1", "peer2", "peer3", "peer4", "peer5"}, "group2 contains first and second user peers")
})
}
func TestAccount_UserGroupsRemoveFromPeers(t *testing.T) {
account := &Account{
Peers: map[string]*Peer{
"peer1": {ID: "peer1", Key: "key1", UserID: "user1"},
"peer2": {ID: "peer2", Key: "key2", UserID: "user1"},
"peer3": {ID: "peer3", Key: "key3", UserID: "user1"},
"peer4": {ID: "peer4", Key: "key4", UserID: "user2"},
"peer5": {ID: "peer5", Key: "key5", UserID: "user2"},
},
Groups: map[string]*Group{
"group1": {ID: "group1", Name: "group1", Issued: GroupIssuedAPI, Peers: []string{"peer1", "peer2", "peer3"}},
"group2": {ID: "group2", Name: "group2", Issued: GroupIssuedAPI, Peers: []string{"peer1", "peer2", "peer3", "peer4", "peer5"}},
"group3": {ID: "group3", Name: "group3", Issued: GroupIssuedAPI, Peers: []string{"peer4", "peer5"}},
},
Users: map[string]*User{"user1": {Id: "user1"}, "user2": {Id: "user2"}},
}
t.Run("remove groups", func(t *testing.T) {
account.UserGroupsRemoveFromPeers("user1", "group1", "group2")
assert.Empty(t, account.Groups["group1"].Peers, "remove all peers from group1")
assert.ElementsMatch(t, account.Groups["group2"].Peers, []string{"peer4", "peer5"}, "group2 contains only second users peers")
})
t.Run("remove group with no peers", func(t *testing.T) {
account.UserGroupsRemoveFromPeers("user1", "group3")
assert.Len(t, account.Groups["group3"].Peers, 2, "peers amount should not change")
})
}
func createManager(t *testing.T) (*DefaultAccountManager, error) {
store, err := createStore(t)
if err != nil {

View File

@@ -1,14 +1,5 @@
package activity
// Activity that triggered an Event
type Activity int
// Code is an activity string representation
type Code struct {
message string
code string
}
const (
// PeerAddedByUser indicates that a user added a new peer to the system
PeerAddedByUser Activity = iota
@@ -106,80 +97,314 @@ const (
UserUnblocked
// GroupDeleted indicates that a user deleted group
GroupDeleted
// UserLoggedInPeer indicates that user logged in their peer with an interactive SSO login
UserLoggedInPeer
// PeerLoginExpired indicates that the user peer login has been expired and peer disconnected
PeerLoginExpired
// DashboardLogin indicates that the user logged in to the dashboard
DashboardLogin
)
var activityMap = map[Activity]Code{
PeerAddedByUser: {"Peer added", "user.peer.add"},
PeerAddedWithSetupKey: {"Peer added", "setupkey.peer.add"},
UserJoined: {"User joined", "user.join"},
UserInvited: {"User invited", "user.invite"},
AccountCreated: {"Account created", "account.create"},
PeerRemovedByUser: {"Peer deleted", "user.peer.delete"},
RuleAdded: {"Rule added", "rule.add"},
RuleUpdated: {"Rule updated", "rule.update"},
RuleRemoved: {"Rule deleted", "rule.delete"},
PolicyAdded: {"Policy added", "policy.add"},
PolicyUpdated: {"Policy updated", "policy.update"},
PolicyRemoved: {"Policy deleted", "policy.delete"},
SetupKeyCreated: {"Setup key created", "setupkey.add"},
SetupKeyUpdated: {"Setup key updated", "setupkey.update"},
SetupKeyRevoked: {"Setup key revoked", "setupkey.revoke"},
SetupKeyOverused: {"Setup key overused", "setupkey.overuse"},
GroupCreated: {"Group created", "group.add"},
GroupUpdated: {"Group updated", "group.update"},
GroupAddedToPeer: {"Group added to peer", "peer.group.add"},
GroupRemovedFromPeer: {"Group removed from peer", "peer.group.delete"},
GroupAddedToUser: {"Group added to user", "user.group.add"},
GroupRemovedFromUser: {"Group removed from user", "user.group.delete"},
UserRoleUpdated: {"User role updated", "user.role.update"},
GroupAddedToSetupKey: {"Group added to setup key", "setupkey.group.add"},
GroupRemovedFromSetupKey: {"Group removed from user setup key", "setupkey.group.delete"},
GroupAddedToDisabledManagementGroups: {"Group added to disabled management DNS setting", "dns.setting.disabled.management.group.add"},
GroupRemovedFromDisabledManagementGroups: {"Group removed from disabled management DNS setting", "dns.setting.disabled.management.group.delete"},
RouteCreated: {"Route created", "route.add"},
RouteRemoved: {"Route deleted", "route.delete"},
RouteUpdated: {"Route updated", "route.update"},
PeerSSHEnabled: {"Peer SSH server enabled", "peer.ssh.enable"},
PeerSSHDisabled: {"Peer SSH server disabled", "peer.ssh.disable"},
PeerRenamed: {"Peer renamed", "peer.rename"},
PeerLoginExpirationEnabled: {"Peer login expiration enabled", "peer.login.expiration.enable"},
PeerLoginExpirationDisabled: {"Peer login expiration disabled", "peer.login.expiration.disable"},
NameserverGroupCreated: {"Nameserver group created", "nameserver.group.add"},
NameserverGroupDeleted: {"Nameserver group deleted", "nameserver.group.delete"},
NameserverGroupUpdated: {"Nameserver group updated", "nameserver.group.update"},
AccountPeerLoginExpirationDurationUpdated: {"Account peer login expiration duration updated", "account.setting.peer.login.expiration.update"},
AccountPeerLoginExpirationEnabled: {"Account peer login expiration enabled", "account.setting.peer.login.expiration.enable"},
AccountPeerLoginExpirationDisabled: {"Account peer login expiration disabled", "account.setting.peer.login.expiration.disable"},
PersonalAccessTokenCreated: {"Personal access token created", "personal.access.token.create"},
PersonalAccessTokenDeleted: {"Personal access token deleted", "personal.access.token.delete"},
ServiceUserCreated: {"Service user created", "service.user.create"},
ServiceUserDeleted: {"Service user deleted", "service.user.delete"},
UserBlocked: {"User blocked", "user.block"},
UserUnblocked: {"User unblocked", "user.unblock"},
GroupDeleted: {"Group deleted", "group.delete"},
UserLoggedInPeer: {"User logged in peer", "user.peer.login"},
PeerLoginExpired: {"Peer login expired", "peer.login.expire"},
DashboardLogin: {"Dashboard login", "dashboard.login"},
const (
// PeerAddedByUserMessage is a human-readable text message of the PeerAddedByUser activity
PeerAddedByUserMessage string = "Peer added"
// PeerAddedWithSetupKeyMessage is a human-readable text message of the PeerAddedWithSetupKey activity
PeerAddedWithSetupKeyMessage = PeerAddedByUserMessage
// UserJoinedMessage is a human-readable text message of the UserJoined activity
UserJoinedMessage string = "User joined"
// UserInvitedMessage is a human-readable text message of the UserInvited activity
UserInvitedMessage string = "User invited"
// AccountCreatedMessage is a human-readable text message of the AccountCreated activity
AccountCreatedMessage string = "Account created"
// PeerRemovedByUserMessage is a human-readable text message of the PeerRemovedByUser activity
PeerRemovedByUserMessage string = "Peer deleted"
// RuleAddedMessage is a human-readable text message of the RuleAdded activity
RuleAddedMessage string = "Rule added"
// RuleRemovedMessage is a human-readable text message of the RuleRemoved activity
RuleRemovedMessage string = "Rule deleted"
// RuleUpdatedMessage is a human-readable text message of the RuleRemoved activity
RuleUpdatedMessage string = "Rule updated"
// PolicyAddedMessage is a human-readable text message of the PolicyAdded activity
PolicyAddedMessage string = "Policy added"
// PolicyRemovedMessage is a human-readable text message of the PolicyRemoved activity
PolicyRemovedMessage string = "Policy deleted"
// PolicyUpdatedMessage is a human-readable text message of the PolicyRemoved activity
PolicyUpdatedMessage string = "Policy updated"
// SetupKeyCreatedMessage is a human-readable text message of the SetupKeyCreated activity
SetupKeyCreatedMessage string = "Setup key created"
// SetupKeyUpdatedMessage is a human-readable text message of the SetupKeyUpdated activity
SetupKeyUpdatedMessage string = "Setup key updated"
// SetupKeyRevokedMessage is a human-readable text message of the SetupKeyRevoked activity
SetupKeyRevokedMessage string = "Setup key revoked"
// SetupKeyOverusedMessage is a human-readable text message of the SetupKeyOverused activity
SetupKeyOverusedMessage string = "Setup key overused"
// GroupCreatedMessage is a human-readable text message of the GroupCreated activity
GroupCreatedMessage string = "Group created"
// GroupUpdatedMessage is a human-readable text message of the GroupUpdated activity
GroupUpdatedMessage string = "Group updated"
// GroupAddedToPeerMessage is a human-readable text message of the GroupAddedToPeer activity
GroupAddedToPeerMessage string = "Group added to peer"
// GroupRemovedFromPeerMessage is a human-readable text message of the GroupRemovedFromPeer activity
GroupRemovedFromPeerMessage string = "Group removed from peer"
// GroupAddedToUserMessage is a human-readable text message of the GroupAddedToUser activity
GroupAddedToUserMessage string = "Group added to user"
// GroupRemovedFromUserMessage is a human-readable text message of the GroupRemovedFromUser activity
GroupRemovedFromUserMessage string = "Group removed from user"
// UserRoleUpdatedMessage is a human-readable text message of the UserRoleUpdatedMessage activity
UserRoleUpdatedMessage string = "User role updated"
// GroupAddedToSetupKeyMessage is a human-readable text message of the GroupAddedToSetupKey activity
GroupAddedToSetupKeyMessage string = "Group added to setup key"
// GroupRemovedFromSetupKeyMessage is a human-readable text message of the GroupRemovedFromSetupKey activity
GroupRemovedFromSetupKeyMessage string = "Group removed from user setup key"
// GroupAddedToDisabledManagementGroupsMessage is a human-readable text message of the GroupAddedToDisabledManagementGroups activity
GroupAddedToDisabledManagementGroupsMessage string = "Group added to disabled management DNS setting"
// GroupRemovedFromDisabledManagementGroupsMessage is a human-readable text message of the GroupRemovedFromDisabledManagementGroups activity
GroupRemovedFromDisabledManagementGroupsMessage string = "Group removed from disabled management DNS setting"
// RouteCreatedMessage is a human-readable text message of the RouteCreated activity
RouteCreatedMessage string = "Route created"
// RouteRemovedMessage is a human-readable text message of the RouteRemoved activity
RouteRemovedMessage string = "Route deleted"
// RouteUpdatedMessage is a human-readable text message of the RouteUpdated activity
RouteUpdatedMessage string = "Route updated"
// PeerSSHEnabledMessage is a human-readable text message of the PeerSSHEnabled activity
PeerSSHEnabledMessage string = "Peer SSH server enabled"
// PeerSSHDisabledMessage is a human-readable text message of the PeerSSHDisabled activity
PeerSSHDisabledMessage string = "Peer SSH server disabled"
// PeerRenamedMessage is a human-readable text message of the PeerRenamed activity
PeerRenamedMessage string = "Peer renamed"
// PeerLoginExpirationDisabledMessage is a human-readable text message of the PeerLoginExpirationDisabled activity
PeerLoginExpirationDisabledMessage string = "Peer login expiration disabled"
// PeerLoginExpirationEnabledMessage is a human-readable text message of the PeerLoginExpirationEnabled activity
PeerLoginExpirationEnabledMessage string = "Peer login expiration enabled"
// NameserverGroupCreatedMessage is a human-readable text message of the NameserverGroupCreated activity
NameserverGroupCreatedMessage string = "Nameserver group created"
// NameserverGroupDeletedMessage is a human-readable text message of the NameserverGroupDeleted activity
NameserverGroupDeletedMessage string = "Nameserver group deleted"
// NameserverGroupUpdatedMessage is a human-readable text message of the NameserverGroupUpdated activity
NameserverGroupUpdatedMessage string = "Nameserver group updated"
// AccountPeerLoginExpirationEnabledMessage is a human-readable text message of the AccountPeerLoginExpirationEnabled activity
AccountPeerLoginExpirationEnabledMessage string = "Peer login expiration enabled for the account"
// AccountPeerLoginExpirationDisabledMessage is a human-readable text message of the AccountPeerLoginExpirationDisabled activity
AccountPeerLoginExpirationDisabledMessage string = "Peer login expiration disabled for the account"
// AccountPeerLoginExpirationDurationUpdatedMessage is a human-readable text message of the AccountPeerLoginExpirationDurationUpdated activity
AccountPeerLoginExpirationDurationUpdatedMessage string = "Peer login expiration duration updated"
// PersonalAccessTokenCreatedMessage is a human-readable text message of the PersonalAccessTokenCreated activity
PersonalAccessTokenCreatedMessage string = "Personal access token created"
// PersonalAccessTokenDeletedMessage is a human-readable text message of the PersonalAccessTokenDeleted activity
PersonalAccessTokenDeletedMessage string = "Personal access token deleted"
// ServiceUserCreatedMessage is a human-readable text message of the ServiceUserCreated activity
ServiceUserCreatedMessage string = "Service user created"
// ServiceUserDeletedMessage is a human-readable text message of the ServiceUserDeleted activity
ServiceUserDeletedMessage string = "Service user deleted"
// UserBlockedMessage is a human-readable text message of the UserBlocked activity
UserBlockedMessage string = "User blocked"
// UserUnblockedMessage is a human-readable text message of the UserUnblocked activity
UserUnblockedMessage string = "User unblocked"
// GroupDeletedMessage is a human-readable text message of the GroupDeleted activity
GroupDeletedMessage string = "Group deleted"
)
// Activity that triggered an Event
type Activity int
// Message returns a string representation of an activity
func (a Activity) Message() string {
switch a {
case PeerAddedByUser:
return PeerAddedByUserMessage
case PeerRemovedByUser:
return PeerRemovedByUserMessage
case PeerAddedWithSetupKey:
return PeerAddedWithSetupKeyMessage
case UserJoined:
return UserJoinedMessage
case UserInvited:
return UserInvitedMessage
case AccountCreated:
return AccountCreatedMessage
case RuleAdded:
return RuleAddedMessage
case RuleRemoved:
return RuleRemovedMessage
case RuleUpdated:
return RuleUpdatedMessage
case PolicyAdded:
return PolicyAddedMessage
case PolicyRemoved:
return PolicyRemovedMessage
case PolicyUpdated:
return PolicyUpdatedMessage
case SetupKeyCreated:
return SetupKeyCreatedMessage
case SetupKeyUpdated:
return SetupKeyUpdatedMessage
case SetupKeyRevoked:
return SetupKeyRevokedMessage
case SetupKeyOverused:
return SetupKeyOverusedMessage
case GroupCreated:
return GroupCreatedMessage
case GroupUpdated:
return GroupUpdatedMessage
case GroupAddedToPeer:
return GroupAddedToPeerMessage
case GroupRemovedFromPeer:
return GroupRemovedFromPeerMessage
case GroupRemovedFromUser:
return GroupRemovedFromUserMessage
case GroupAddedToUser:
return GroupAddedToUserMessage
case UserRoleUpdated:
return UserRoleUpdatedMessage
case GroupAddedToSetupKey:
return GroupAddedToSetupKeyMessage
case GroupRemovedFromSetupKey:
return GroupRemovedFromSetupKeyMessage
case GroupAddedToDisabledManagementGroups:
return GroupAddedToDisabledManagementGroupsMessage
case GroupRemovedFromDisabledManagementGroups:
return GroupRemovedFromDisabledManagementGroupsMessage
case RouteCreated:
return RouteCreatedMessage
case RouteRemoved:
return RouteRemovedMessage
case RouteUpdated:
return RouteUpdatedMessage
case PeerSSHEnabled:
return PeerSSHEnabledMessage
case PeerSSHDisabled:
return PeerSSHDisabledMessage
case PeerLoginExpirationEnabled:
return PeerLoginExpirationEnabledMessage
case PeerLoginExpirationDisabled:
return PeerLoginExpirationDisabledMessage
case PeerRenamed:
return PeerRenamedMessage
case NameserverGroupCreated:
return NameserverGroupCreatedMessage
case NameserverGroupDeleted:
return NameserverGroupDeletedMessage
case NameserverGroupUpdated:
return NameserverGroupUpdatedMessage
case AccountPeerLoginExpirationEnabled:
return AccountPeerLoginExpirationEnabledMessage
case AccountPeerLoginExpirationDisabled:
return AccountPeerLoginExpirationDisabledMessage
case AccountPeerLoginExpirationDurationUpdated:
return AccountPeerLoginExpirationDurationUpdatedMessage
case PersonalAccessTokenCreated:
return PersonalAccessTokenCreatedMessage
case PersonalAccessTokenDeleted:
return PersonalAccessTokenDeletedMessage
case ServiceUserCreated:
return ServiceUserCreatedMessage
case ServiceUserDeleted:
return ServiceUserDeletedMessage
case UserBlocked:
return UserBlockedMessage
case UserUnblocked:
return UserUnblockedMessage
case GroupDeleted:
return GroupDeletedMessage
default:
return "UNKNOWN_ACTIVITY"
}
}
// StringCode returns a string code of the activity
func (a Activity) StringCode() string {
if code, ok := activityMap[a]; ok {
return code.code
switch a {
case PeerAddedByUser:
return "user.peer.add"
case PeerRemovedByUser:
return "user.peer.delete"
case PeerAddedWithSetupKey:
return "setupkey.peer.add"
case UserJoined:
return "user.join"
case UserInvited:
return "user.invite"
case UserBlocked:
return "user.block"
case UserUnblocked:
return "user.unblock"
case AccountCreated:
return "account.create"
case RuleAdded:
return "rule.add"
case RuleRemoved:
return "rule.delete"
case RuleUpdated:
return "rule.update"
case PolicyAdded:
return "policy.add"
case PolicyRemoved:
return "policy.delete"
case PolicyUpdated:
return "policy.update"
case SetupKeyCreated:
return "setupkey.add"
case SetupKeyRevoked:
return "setupkey.revoke"
case SetupKeyOverused:
return "setupkey.overuse"
case SetupKeyUpdated:
return "setupkey.update"
case GroupCreated:
return "group.add"
case GroupUpdated:
return "group.update"
case GroupDeleted:
return "group.delete"
case GroupRemovedFromPeer:
return "peer.group.delete"
case GroupAddedToPeer:
return "peer.group.add"
case GroupAddedToUser:
return "user.group.add"
case GroupRemovedFromUser:
return "user.group.delete"
case UserRoleUpdated:
return "user.role.update"
case GroupAddedToSetupKey:
return "setupkey.group.add"
case GroupRemovedFromSetupKey:
return "setupkey.group.delete"
case GroupAddedToDisabledManagementGroups:
return "dns.setting.disabled.management.group.add"
case GroupRemovedFromDisabledManagementGroups:
return "dns.setting.disabled.management.group.delete"
case RouteCreated:
return "route.add"
case RouteRemoved:
return "route.delete"
case RouteUpdated:
return "route.update"
case PeerRenamed:
return "peer.rename"
case PeerSSHEnabled:
return "peer.ssh.enable"
case PeerSSHDisabled:
return "peer.ssh.disable"
case PeerLoginExpirationDisabled:
return "peer.login.expiration.disable"
case PeerLoginExpirationEnabled:
return "peer.login.expiration.enable"
case NameserverGroupCreated:
return "nameserver.group.add"
case NameserverGroupDeleted:
return "nameserver.group.delete"
case NameserverGroupUpdated:
return "nameserver.group.update"
case AccountPeerLoginExpirationDurationUpdated:
return "account.setting.peer.login.expiration.update"
case AccountPeerLoginExpirationEnabled:
return "account.setting.peer.login.expiration.enable"
case AccountPeerLoginExpirationDisabled:
return "account.setting.peer.login.expiration.disable"
case PersonalAccessTokenCreated:
return "personal.access.token.create"
case PersonalAccessTokenDeleted:
return "personal.access.token.delete"
case ServiceUserCreated:
return "service.user.create"
case ServiceUserDeleted:
return "service.user.delete"
default:
return "UNKNOWN_ACTIVITY"
}
return "UNKNOWN_ACTIVITY"
}
// Message returns a string representation of an activity
func (a Activity) Message() string {
if code, ok := activityMap[a]; ok {
return code.message
}
return "UNKNOWN_ACTIVITY"
}

View File

@@ -570,26 +570,6 @@ func (s *FileStore) SavePeerStatus(accountID, peerID string, peerStatus PeerStat
return nil
}
// SaveUserLastLogin stores the last login time for a user in memory. It doesn't attempt to persist data to speed up things.
func (s *FileStore) SaveUserLastLogin(accountID, userID string, lastLogin time.Time) error {
s.mux.Lock()
defer s.mux.Unlock()
account, err := s.getAccount(accountID)
if err != nil {
return err
}
peer := account.Users[userID]
if peer == nil {
return status.Errorf(status.NotFound, "user %s not found", userID)
}
peer.LastLogin = lastLogin
return nil
}
// Close the FileStore persisting data to disk
func (s *FileStore) Close() error {
s.mux.Lock()

View File

@@ -59,14 +59,12 @@ func (g *Group) EventMeta() map[string]any {
}
func (g *Group) Copy() *Group {
group := &Group{
return &Group{
ID: g.ID,
Name: g.Name,
Issued: g.Issued,
Peers: make([]string, len(g.Peers)),
Peers: g.Peers[:],
}
copy(group.Peers, g.Peers)
return group
}
// GetGroup object of the peers

View File

@@ -80,14 +80,12 @@ func (h *AccountsHandler) UpdateAccount(w http.ResponseWriter, r *http.Request)
if req.Settings.JwtGroupsEnabled != nil {
settings.JWTGroupsEnabled = *req.Settings.JwtGroupsEnabled
}
if req.Settings.GroupsPropagationEnabled != nil {
settings.GroupsPropagationEnabled = *req.Settings.GroupsPropagationEnabled
}
if req.Settings.JwtGroupsClaimName != nil {
settings.JWTGroupsClaimName = *req.Settings.JwtGroupsClaimName
}
updatedAccount, err := h.accountManager.UpdateAccountSettings(accountID, user.Id, settings)
if err != nil {
util.WriteError(err, w)
return
@@ -104,7 +102,6 @@ func toAccountResponse(account *server.Account) *api.Account {
Settings: api.AccountSettings{
PeerLoginExpiration: int(account.Settings.PeerLoginExpiration.Seconds()),
PeerLoginExpirationEnabled: account.Settings.PeerLoginExpirationEnabled,
GroupsPropagationEnabled: &account.Settings.GroupsPropagationEnabled,
JwtGroupsEnabled: &account.Settings.JWTGroupsEnabled,
JwtGroupsClaimName: &account.Settings.JWTGroupsClaimName,
},

View File

@@ -38,6 +38,7 @@ func initAccountsTestData(account *server.Account, admin *server.User) *Accounts
accCopy := account.Copy()
accCopy.UpdateSettings(newSettings)
return accCopy, nil
},
},
claimsExtractor: jwtclaims.NewClaimsExtractor(
@@ -53,6 +54,7 @@ func initAccountsTestData(account *server.Account, admin *server.User) *Accounts
}
func TestAccounts_AccountsHandler(t *testing.T) {
accountID := "test_account"
adminUser := server.NewAdminUser("test_user")
@@ -92,7 +94,6 @@ func TestAccounts_AccountsHandler(t *testing.T) {
expectedSettings: api.AccountSettings{
PeerLoginExpiration: int(time.Hour.Seconds()),
PeerLoginExpirationEnabled: false,
GroupsPropagationEnabled: br(false),
JwtGroupsClaimName: sr(""),
JwtGroupsEnabled: br(false),
},
@@ -109,7 +110,6 @@ func TestAccounts_AccountsHandler(t *testing.T) {
expectedSettings: api.AccountSettings{
PeerLoginExpiration: 15552000,
PeerLoginExpirationEnabled: true,
GroupsPropagationEnabled: br(false),
JwtGroupsClaimName: sr(""),
JwtGroupsEnabled: br(false),
},
@@ -126,30 +126,12 @@ func TestAccounts_AccountsHandler(t *testing.T) {
expectedSettings: api.AccountSettings{
PeerLoginExpiration: 15552000,
PeerLoginExpirationEnabled: false,
GroupsPropagationEnabled: br(false),
JwtGroupsClaimName: sr("roles"),
JwtGroupsEnabled: br(true),
},
expectedArray: false,
expectedID: accountID,
},
{
name: "PutAccount OK wiht JWT Propagation",
expectedBody: true,
requestType: http.MethodPut,
requestPath: "/api/accounts/" + accountID,
requestBody: bytes.NewBufferString("{\"settings\": {\"peer_login_expiration\": 554400,\"peer_login_expiration_enabled\": true,\"jwt_groups_enabled\":true,\"jwt_groups_claim_name\":\"groups\",\"groups_propagation_enabled\":true}}"),
expectedStatus: http.StatusOK,
expectedSettings: api.AccountSettings{
PeerLoginExpiration: 554400,
PeerLoginExpirationEnabled: true,
GroupsPropagationEnabled: br(true),
JwtGroupsClaimName: sr("groups"),
JwtGroupsEnabled: br(true),
},
expectedArray: false,
expectedID: accountID,
},
{
name: "Update account failure with high peer_login_expiration more than 180 days",
expectedBody: true,

View File

@@ -54,10 +54,6 @@ components:
description: Period of time after which peer login expires (seconds).
type: integer
example: 43200
groups_propagation_enabled:
description: Allows propagate the new user auto groups to peers that belongs to the user
type: boolean
example: true
jwt_groups_enabled:
description: Allows extract groups from JWT claim and add it to account groups.
type: boolean
@@ -100,11 +96,6 @@ components:
type: string
enum: [ "active","invited","blocked" ]
example: active
last_login:
description: Last time this user performed a login to the dashboard
type: string
format: date-time
example: 2023-05-05T09:00:35.477782Z
auto_groups:
description: Groups to auto-assign to peers registered by this user
type: array
@@ -378,9 +369,7 @@ components:
expires_in:
description: Expiration time in seconds
type: integer
minimum: 86400
maximum: 31536000
example: 86400
example: 43200
revoked:
description: Setup key revocation status
type: boolean
@@ -897,7 +886,7 @@ components:
description: The string code of the activity that occurred during the event
type: string
enum: [ "user.peer.delete", "user.join", "user.invite", "user.peer.add", "user.group.add", "user.group.delete",
"user.role.update", "user.block", "user.unblock", "user.peer.login",
"user.role.update", "user.block", "user.unblock",
"setupkey.peer.add", "setupkey.add", "setupkey.update", "setupkey.revoke", "setupkey.overuse",
"setupkey.group.delete", "setupkey.group.add",
"rule.add", "rule.delete", "rule.update",
@@ -906,7 +895,7 @@ components:
"account.create", "account.setting.peer.login.expiration.update", "account.setting.peer.login.expiration.disable", "account.setting.peer.login.expiration.enable",
"route.add", "route.delete", "route.update",
"nameserver.group.add", "nameserver.group.delete", "nameserver.group.update",
"peer.ssh.disable", "peer.ssh.enable", "peer.rename", "peer.login.expiration.disable", "peer.login.expiration.enable", "peer.login.expire",
"peer.ssh.disable", "peer.ssh.enable", "peer.rename", "peer.login.expiration.disable", "peer.login.expiration.enable",
"service.user.create", "personal.access.token.create", "service.user.delete", "personal.access.token.delete" ]
example: route.add
initiator_id:

View File

@@ -27,7 +27,6 @@ const (
EventActivityCodeNameserverGroupUpdate EventActivityCode = "nameserver.group.update"
EventActivityCodePeerLoginExpirationDisable EventActivityCode = "peer.login.expiration.disable"
EventActivityCodePeerLoginExpirationEnable EventActivityCode = "peer.login.expiration.enable"
EventActivityCodePeerLoginExpire EventActivityCode = "peer.login.expire"
EventActivityCodePeerRename EventActivityCode = "peer.rename"
EventActivityCodePeerSshDisable EventActivityCode = "peer.ssh.disable"
EventActivityCodePeerSshEnable EventActivityCode = "peer.ssh.enable"
@@ -58,7 +57,6 @@ const (
EventActivityCodeUserJoin EventActivityCode = "user.join"
EventActivityCodeUserPeerAdd EventActivityCode = "user.peer.add"
EventActivityCodeUserPeerDelete EventActivityCode = "user.peer.delete"
EventActivityCodeUserPeerLogin EventActivityCode = "user.peer.login"
EventActivityCodeUserRoleUpdate EventActivityCode = "user.role.update"
EventActivityCodeUserUnblock EventActivityCode = "user.unblock"
)
@@ -131,9 +129,6 @@ type AccountRequest struct {
// AccountSettings defines model for AccountSettings.
type AccountSettings struct {
// GroupsPropagationEnabled Allows propagate the new user auto groups to peers that belongs to the user
GroupsPropagationEnabled *bool `json:"groups_propagation_enabled,omitempty"`
// JwtGroupsClaimName Name of the claim from which we extract groups names to add it to account groups.
JwtGroupsClaimName *string `json:"jwt_groups_claim_name,omitempty"`
@@ -767,9 +762,6 @@ type User struct {
// IsServiceUser Is true if this user is a service user
IsServiceUser *bool `json:"is_service_user,omitempty"`
// LastLogin Last time this user performed a login to the dashboard
LastLogin *time.Time `json:"last_login,omitempty"`
// Name User's name from idp provider
Name string `json:"name"`

View File

@@ -54,7 +54,6 @@ var baseExistingNSGroup = &nbdns.NameServerGroup{
},
},
Groups: []string{"testing"},
Domains: []string{"domain"},
Enabled: true,
}

View File

@@ -60,13 +60,6 @@ func (h *SetupKeysHandler) CreateSetupKey(w http.ResponseWriter, r *http.Request
expiresIn := time.Duration(req.ExpiresIn) * time.Second
day := time.Hour * 24
year := day * 365
if expiresIn < day || expiresIn > year {
util.WriteError(status.Errorf(status.InvalidArgument, "expiresIn should be between 1 day and 365 days"), w)
return
}
if req.AutoGroups == nil {
req.AutoGroups = []string{}
}

View File

@@ -143,7 +143,7 @@ func TestSetupKeysHandlers(t *testing.T) {
requestType: http.MethodPost,
requestPath: "/api/setup-keys",
requestBody: bytes.NewBuffer(
[]byte(fmt.Sprintf("{\"name\":\"%s\",\"type\":\"%s\",\"expires_in\":86400}", newSetupKey.Name, newSetupKey.Type))),
[]byte(fmt.Sprintf("{\"name\":\"%s\",\"type\":\"%s\"}", newSetupKey.Name, newSetupKey.Type))),
expectedStatus: http.StatusOK,
expectedBody: true,
expectedSetupKey: toResponseBody(newSetupKey),

View File

@@ -270,6 +270,5 @@ func toUserResponse(user *server.UserInfo, currenUserID string) *api.User {
IsCurrent: &isCurrent,
IsServiceUser: &user.IsServiceUser,
IsBlocked: user.IsBlocked,
LastLogin: &user.LastLogin,
}
}

View File

@@ -461,7 +461,7 @@ func (am *Auth0Manager) UpdateUserAppMetadata(userID string, appMetadata AppMeta
return nil
}
func buildCreateUserRequestPayload(email, name, accountID, invitedByEmail string) (string, error) {
func buildCreateUserRequestPayload(email string, name string, accountID string) (string, error) {
invite := true
req := &createUserRequest{
Email: email,
@@ -469,7 +469,6 @@ func buildCreateUserRequestPayload(email, name, accountID, invitedByEmail string
AppMeta: AppMetadata{
WTAccountID: accountID,
WTPendingInvite: &invite,
WTInvitedBy: invitedByEmail,
},
Connection: "Username-Password-Authentication",
Password: GeneratePassword(8, 1, 1, 1),
@@ -635,9 +634,9 @@ func (am *Auth0Manager) GetUserByEmail(email string) ([]*UserData, error) {
}
// CreateUser creates a new user in Auth0 Idp and sends an invite
func (am *Auth0Manager) CreateUser(email, name, accountID, invitedByEmail string) (*UserData, error) {
func (am *Auth0Manager) CreateUser(email string, name string, accountID string) (*UserData, error) {
payloadString, err := buildCreateUserRequestPayload(email, name, accountID, invitedByEmail)
payloadString, err := buildCreateUserRequestPayload(email, name, accountID)
if err != nil {
return nil, err
}

View File

@@ -343,7 +343,7 @@ func TestAuth0_UpdateUserAppMetadata(t *testing.T) {
updateUserAppMetadataTestCase2 := updateUserAppMetadataTest{
name: "Bad Status Code",
inputReqBody: fmt.Sprintf("{\"access_token\":\"%s\",\"scope\":\"read:users\",\"expires_in\":%d,\"token_type\":\"Bearer\"}", token, exp),
expectedReqBody: fmt.Sprintf("{\"app_metadata\":{\"wt_account_id\":\"%s\",\"wt_pending_invite\":null,\"wt_invited_by_email\":\"\"}}", appMetadata.WTAccountID),
expectedReqBody: fmt.Sprintf("{\"app_metadata\":{\"wt_account_id\":\"%s\",\"wt_pending_invite\":null}}", appMetadata.WTAccountID),
appMetadata: appMetadata,
statusCode: 400,
helper: JsonParser{},
@@ -366,7 +366,7 @@ func TestAuth0_UpdateUserAppMetadata(t *testing.T) {
updateUserAppMetadataTestCase4 := updateUserAppMetadataTest{
name: "Good request",
inputReqBody: fmt.Sprintf("{\"access_token\":\"%s\",\"scope\":\"read:users\",\"expires_in\":%d,\"token_type\":\"Bearer\"}", token, exp),
expectedReqBody: fmt.Sprintf("{\"app_metadata\":{\"wt_account_id\":\"%s\",\"wt_pending_invite\":null,\"wt_invited_by_email\":\"\"}}", appMetadata.WTAccountID),
expectedReqBody: fmt.Sprintf("{\"app_metadata\":{\"wt_account_id\":\"%s\",\"wt_pending_invite\":null}}", appMetadata.WTAccountID),
appMetadata: appMetadata,
statusCode: 200,
helper: JsonParser{},
@@ -378,7 +378,7 @@ func TestAuth0_UpdateUserAppMetadata(t *testing.T) {
updateUserAppMetadataTestCase5 := updateUserAppMetadataTest{
name: "Update Pending Invite",
inputReqBody: fmt.Sprintf("{\"access_token\":\"%s\",\"scope\":\"read:users\",\"expires_in\":%d,\"token_type\":\"Bearer\"}", token, exp),
expectedReqBody: fmt.Sprintf("{\"app_metadata\":{\"wt_account_id\":\"%s\",\"wt_pending_invite\":true,\"wt_invited_by_email\":\"\"}}", appMetadata.WTAccountID),
expectedReqBody: fmt.Sprintf("{\"app_metadata\":{\"wt_account_id\":\"%s\",\"wt_pending_invite\":true}}", appMetadata.WTAccountID),
appMetadata: AppMetadata{
WTAccountID: "ok",
WTPendingInvite: &invite,

View File

@@ -362,7 +362,7 @@ func (am *AuthentikManager) GetAllAccounts() (map[string][]*UserData, error) {
}
// CreateUser creates a new user in authentik Idp and sends an invitation.
func (am *AuthentikManager) CreateUser(email, name, accountID, invitedByEmail string) (*UserData, error) {
func (am *AuthentikManager) CreateUser(email string, name string, accountID string) (*UserData, error) {
ctx, err := am.authenticationContext()
if err != nil {
return nil, err

View File

@@ -236,7 +236,7 @@ func (ac *AzureCredentials) Authenticate() (JWTToken, error) {
}
// CreateUser creates a new user in azure AD Idp.
func (am *AzureManager) CreateUser(email, name, accountID, invitedByEmail string) (*UserData, error) {
func (am *AzureManager) CreateUser(email string, name string, accountID string) (*UserData, error) {
payload, err := buildAzureCreateUserRequestPayload(email, name, accountID, am.ClientID)
if err != nil {
return nil, err

View File

@@ -185,7 +185,7 @@ func (gm *GoogleWorkspaceManager) GetAllAccounts() (map[string][]*UserData, erro
}
// CreateUser creates a new user in Google Workspace and sends an invitation.
func (gm *GoogleWorkspaceManager) CreateUser(email, name, accountID, invitedByEmail string) (*UserData, error) {
func (gm *GoogleWorkspaceManager) CreateUser(email string, name string, accountID string) (*UserData, error) {
invite := true
metadata := AppMetadata{
WTAccountID: accountID,

View File

@@ -15,7 +15,7 @@ type Manager interface {
GetUserDataByID(userId string, appMetadata AppMetadata) (*UserData, error)
GetAccount(accountId string) ([]*UserData, error)
GetAllAccounts() (map[string][]*UserData, error)
CreateUser(email, name, accountID, invitedByEmail string) (*UserData, error)
CreateUser(email string, name string, accountID string) (*UserData, error)
GetUserByEmail(email string) ([]*UserData, error)
InviteUserByID(userID string) error
}
@@ -72,7 +72,6 @@ type AppMetadata struct {
// maps to wt_account_id when json.marshal
WTAccountID string `json:"wt_account_id,omitempty"`
WTPendingInvite *bool `json:"wt_pending_invite"`
WTInvitedBy string `json:"wt_invited_by_email"`
}
// JWTToken a JWT object that holds information of a token

View File

@@ -230,7 +230,7 @@ func (kc *KeycloakCredentials) Authenticate() (JWTToken, error) {
}
// CreateUser creates a new user in keycloak Idp and sends an invite.
func (km *KeycloakManager) CreateUser(email, name, accountID, invitedByEmail string) (*UserData, error) {
func (km *KeycloakManager) CreateUser(email string, name string, accountID string) (*UserData, error) {
jwtToken, err := km.credentials.Authenticate()
if err != nil {
return nil, err

View File

@@ -103,7 +103,7 @@ func (oc *OktaCredentials) Authenticate() (JWTToken, error) {
}
// CreateUser creates a new user in okta Idp and sends an invitation.
func (om *OktaManager) CreateUser(email, name, accountID, invitedByEmail string) (*UserData, error) {
func (om *OktaManager) CreateUser(email string, name string, accountID string) (*UserData, error) {
var (
sendEmail = true
activate = true

View File

@@ -234,7 +234,7 @@ func (zc *ZitadelCredentials) Authenticate() (JWTToken, error) {
}
// CreateUser creates a new user in zitadel Idp and sends an invite.
func (zm *ZitadelManager) CreateUser(email, name, accountID, invitedByEmail string) (*UserData, error) {
func (zm *ZitadelManager) CreateUser(email string, name string, accountID string) (*UserData, error) {
payload, err := buildZitadelCreateUserRequestPayload(email, name)
if err != nil {
return nil, err

View File

@@ -1,8 +1,6 @@
package jwtclaims
import (
"time"
"github.com/golang-jwt/jwt"
)
@@ -12,7 +10,6 @@ type AuthorizationClaims struct {
AccountId string
Domain string
DomainCategory string
LastLogin time.Time
Raw jwt.MapClaims
}

View File

@@ -2,7 +2,6 @@ package jwtclaims
import (
"net/http"
"time"
"github.com/golang-jwt/jwt"
)
@@ -18,8 +17,6 @@ const (
DomainCategorySuffix = "wt_account_domain_category"
// UserIDClaim claim for the user id
UserIDClaim = "sub"
// LastLoginSuffix claim for the last login
LastLoginSuffix = "nb_last_login"
)
// ExtractClaims Extract function type
@@ -96,24 +93,9 @@ func (c *ClaimsExtractor) FromToken(token *jwt.Token) AuthorizationClaims {
if ok {
jwtClaims.DomainCategory = domainCategoryClaim.(string)
}
LastLoginClaimString, ok := claims[c.authAudience+LastLoginSuffix]
if ok {
jwtClaims.LastLogin = parseTime(LastLoginClaimString.(string))
}
return jwtClaims
}
func parseTime(timeString string) time.Time {
if timeString == "" {
return time.Time{}
}
parsedTime, err := time.Parse(time.RFC3339, timeString)
if err != nil {
return time.Time{}
}
return parsedTime
}
// fromRequestContext extracts claims from the request context previously filled by the JWT token (after auth)
func (c *ClaimsExtractor) fromRequestContext(r *http.Request) AuthorizationClaims {
if r.Context().Value(TokenUserProperty) == nil {

View File

@@ -4,15 +4,12 @@ import (
"context"
"net/http"
"testing"
"time"
"github.com/golang-jwt/jwt"
"github.com/stretchr/testify/require"
)
func newTestRequestWithJWT(t *testing.T, claims AuthorizationClaims, audiance string) *http.Request {
const layout = "2006-01-02T15:04:05.999Z"
claimMaps := jwt.MapClaims{}
if claims.UserId != "" {
claimMaps[UserIDClaim] = claims.UserId
@@ -26,9 +23,6 @@ func newTestRequestWithJWT(t *testing.T, claims AuthorizationClaims, audiance st
if claims.DomainCategory != "" {
claimMaps[audiance+DomainCategorySuffix] = claims.DomainCategory
}
if claims.LastLogin != (time.Time{}) {
claimMaps[audiance+LastLoginSuffix] = claims.LastLogin.Format(layout)
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claimMaps)
r, err := http.NewRequest(http.MethodGet, "http://localhost", nil)
require.NoError(t, err, "creating testing request failed")
@@ -46,9 +40,6 @@ func TestExtractClaimsFromRequestContext(t *testing.T) {
expectedMSG string
}
const layout = "2006-01-02T15:04:05.999Z"
lastLogin, _ := time.Parse(layout, "2023-08-17T09:30:40.465Z")
testCase1 := test{
name: "All Claim Fields",
inputAudiance: "https://login/",
@@ -56,13 +47,11 @@ func TestExtractClaimsFromRequestContext(t *testing.T) {
UserId: "test",
Domain: "test.com",
AccountId: "testAcc",
LastLogin: lastLogin,
DomainCategory: "public",
Raw: jwt.MapClaims{
"https://login/wt_account_domain": "test.com",
"https://login/wt_account_domain_category": "public",
"https://login/wt_account_id": "testAcc",
"https://login/nb_last_login": lastLogin.Format(layout),
"sub": "test",
},
},

View File

@@ -1,18 +1,14 @@
package server
import (
"errors"
"regexp"
"strconv"
"unicode/utf8"
"github.com/miekg/dns"
"github.com/rs/xid"
log "github.com/sirupsen/logrus"
nbdns "github.com/netbirdio/netbird/dns"
"github.com/netbirdio/netbird/management/server/activity"
"github.com/netbirdio/netbird/management/server/status"
"github.com/rs/xid"
log "github.com/sirupsen/logrus"
"strconv"
"unicode/utf8"
)
const (
@@ -30,8 +26,6 @@ const (
UpdateNameServerGroupPrimary
// UpdateNameServerGroupDomains indicates a nameserver group' domains update operation
UpdateNameServerGroupDomains
domainPattern = `^(?i)[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,}$`
)
// NameServerGroupUpdateOperationType operation type
@@ -370,8 +364,9 @@ func validateDomainInput(primary bool, domains []string) error {
" you should set either primary or domain")
}
for _, domain := range domains {
if err := validateDomain(domain); err != nil {
return status.Errorf(status.InvalidArgument, "nameserver group got an invalid domain: %s %q", domain, err)
_, valid := dns.IsDomainName(domain)
if !valid {
return status.Errorf(status.InvalidArgument, "nameserver group got an invalid domain: %s", domain)
}
}
return nil
@@ -422,21 +417,3 @@ func validateGroups(list []string, groups map[string]*Group) error {
return nil
}
func validateDomain(domain string) error {
domainMatcher := regexp.MustCompile(domainPattern)
if !domainMatcher.MatchString(domain) {
return errors.New("domain should consists of only letters, numbers, and hyphens with no leading, trailing hyphens, or spaces")
}
labels, valid := dns.IsDomainName(domain)
if !valid {
return errors.New("invalid domain name")
}
if labels < 2 {
return errors.New("domain should consists of a minimum of two labels")
}
return nil
}

View File

@@ -1160,69 +1160,3 @@ func initTestNSAccount(t *testing.T, am *DefaultAccountManager) (*Account, error
return account, nil
}
func TestValidateDomain(t *testing.T) {
testCases := []struct {
name string
domain string
errFunc require.ErrorAssertionFunc
}{
{
name: "Valid domain name with multiple labels",
domain: "123.example.com",
errFunc: require.NoError,
},
{
name: "Valid domain name with hyphen",
domain: "test-example.com",
errFunc: require.NoError,
},
{
name: "Invalid domain name with double hyphen",
domain: "test--example.com",
errFunc: require.Error,
},
{
name: "Invalid domain name with only one label",
domain: "com",
errFunc: require.Error,
},
{
name: "Invalid domain name with a label exceeding 63 characters",
domain: "dnsdnsdnsdnsdnsdnsdnsdnsdnsdnsdnsdnsdnsdnsdnsdnsdnsdnsdnsdnsdnsdns.com",
errFunc: require.Error,
},
{
name: "Invalid domain name starting with a hyphen",
domain: "-example.com",
errFunc: require.Error,
},
{
name: "Invalid domain name ending with a hyphen",
domain: "example.com-",
errFunc: require.Error,
},
{
name: "Invalid domain with unicode",
domain: "example?,.com",
errFunc: require.Error,
},
{
name: "Invalid domain with space before top-level domain",
domain: "space .example.com",
errFunc: require.Error,
},
{
name: "Invalid domain with trailing space",
domain: "example.com ",
errFunc: require.Error,
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
testCase.errFunc(t, validateDomain(testCase.domain))
})
}
}

View File

@@ -108,10 +108,6 @@ func (p *Peer) AddedWithSSOLogin() bool {
// Copy copies Peer object
func (p *Peer) Copy() *Peer {
peerStatus := p.Status
if peerStatus != nil {
peerStatus = p.Status.Copy()
}
return &Peer{
ID: p.ID,
Key: p.Key,
@@ -119,11 +115,11 @@ func (p *Peer) Copy() *Peer {
IP: p.IP,
Meta: p.Meta,
Name: p.Name,
DNSLabel: p.DNSLabel,
Status: peerStatus,
Status: p.Status,
UserID: p.UserID,
SSHKey: p.SSHKey,
SSHEnabled: p.SSHEnabled,
DNSLabel: p.DNSLabel,
LoginExpirationEnabled: p.LoginExpirationEnabled,
LastLogin: p.LastLogin,
}
@@ -699,8 +695,6 @@ func (am *DefaultAccountManager) LoginPeer(login PeerLogin) (*Peer, *NetworkMap,
updatePeerLastLogin(peer, account)
updateRemotePeers = true
shouldStoreAccount = true
am.storeEvent(login.UserID, peer.ID, account.Id, activity.UserLoggedInPeer, peer.EventMeta(am.GetDNSDomain()))
}
peer, updated := updatePeerMeta(peer, login.Meta, account)

View File

@@ -36,18 +36,6 @@ type PersonalAccessToken struct {
LastUsed time.Time
}
func (t *PersonalAccessToken) Copy() *PersonalAccessToken {
return &PersonalAccessToken{
ID: t.ID,
Name: t.Name,
HashedToken: t.HashedToken,
ExpirationDate: t.ExpirationDate,
CreatedBy: t.CreatedBy,
CreatedAt: t.CreatedAt,
LastUsed: t.LastUsed,
}
}
// PersonalAccessTokenGenerated holds the new PersonalAccessToken and the plain text version of it
type PersonalAccessTokenGenerated struct {
PlainToken string

View File

@@ -95,22 +95,18 @@ type PolicyRule struct {
// Copy returns a copy of a policy rule
func (pm *PolicyRule) Copy() *PolicyRule {
rule := &PolicyRule{
return &PolicyRule{
ID: pm.ID,
Name: pm.Name,
Description: pm.Description,
Enabled: pm.Enabled,
Action: pm.Action,
Destinations: make([]string, len(pm.Destinations)),
Sources: make([]string, len(pm.Sources)),
Destinations: pm.Destinations[:],
Sources: pm.Sources[:],
Bidirectional: pm.Bidirectional,
Protocol: pm.Protocol,
Ports: make([]string, len(pm.Ports)),
Ports: pm.Ports[:],
}
copy(rule.Destinations, pm.Destinations)
copy(rule.Sources, pm.Sources)
copy(rule.Ports, pm.Ports)
return rule
}
// ToRule converts the PolicyRule to a legacy representation of the Rule (for backwards compatibility)
@@ -151,10 +147,9 @@ func (p *Policy) Copy() *Policy {
Name: p.Name,
Description: p.Description,
Enabled: p.Enabled,
Rules: make([]*PolicyRule, len(p.Rules)),
}
for i, r := range p.Rules {
c.Rules[i] = r.Copy()
for _, r := range p.Rules {
c.Rules = append(c.Rules, r.Copy())
}
return c
}

View File

@@ -45,18 +45,15 @@ type Rule struct {
}
func (r *Rule) Copy() *Rule {
rule := &Rule{
return &Rule{
ID: r.ID,
Name: r.Name,
Description: r.Description,
Disabled: r.Disabled,
Source: make([]string, len(r.Source)),
Destination: make([]string, len(r.Destination)),
Source: r.Source[:],
Destination: r.Destination[:],
Flow: r.Flow,
}
copy(rule.Source, r.Source)
copy(rule.Destination, r.Destination)
return rule
}
// EventMeta returns activity event meta related to this rule

View File

@@ -90,8 +90,8 @@ type SetupKey struct {
// Copy copies SetupKey to a new object
func (key *SetupKey) Copy() *SetupKey {
autoGroups := make([]string, len(key.AutoGroups))
copy(autoGroups, key.AutoGroups)
autoGroups := make([]string, 0)
autoGroups = append(autoGroups, key.AutoGroups...)
if key.UpdatedAt.IsZero() {
key.UpdatedAt = key.CreatedAt
}

View File

@@ -1,7 +1,5 @@
package server
import "time"
type Store interface {
GetAllAccounts() []*Account
GetAccount(accountID string) (*Account, error)
@@ -22,7 +20,6 @@ type Store interface {
// AcquireGlobalLock should attempt to acquire a global lock and return a function that releases the lock
AcquireGlobalLock() func()
SavePeerStatus(accountID, peerID string, status PeerStatus) error
SaveUserLastLogin(accountID, userID string, lastLogin time.Time) error
// Close should close the store persisting all unsaved data.
Close() error
}

View File

@@ -1,11 +1,9 @@
package server
import (
"sync"
log "github.com/sirupsen/logrus"
"github.com/netbirdio/netbird/management/proto"
log "github.com/sirupsen/logrus"
"sync"
)
const channelBufferSize = 100
@@ -35,7 +33,7 @@ func (p *PeersUpdateManager) SendUpdate(peerID string, update *UpdateMessage) er
if channel, ok := p.peerChannels[peerID]; ok {
select {
case channel <- update:
log.Debugf("update was sent to channel for peer %s", peerID)
log.Infof("update was sent to channel for peer %s", peerID)
default:
log.Warnf("channel for peer %s is %d full", peerID, len(channel))
}
@@ -54,7 +52,7 @@ func (p *PeersUpdateManager) CreateChannel(peerID string) chan *UpdateMessage {
delete(p.peerChannels, peerID)
close(channel)
}
// mbragin: todo shouldn't it be more? or configurable?
//mbragin: todo shouldn't it be more? or configurable?
channel := make(chan *UpdateMessage, channelBufferSize)
p.peerChannels[peerID] = channel

View File

@@ -3,7 +3,6 @@ package server
import (
"fmt"
"strings"
"time"
"github.com/google/uuid"
log "github.com/sirupsen/logrus"
@@ -54,8 +53,6 @@ type User struct {
PATs map[string]*PersonalAccessToken
// 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
}
// IsBlocked returns true if the user is blocked, false otherwise
@@ -63,10 +60,6 @@ func (u *User) IsBlocked() bool {
return u.Blocked
}
func (u *User) LastDashboardLoginChanged(LastLogin time.Time) bool {
return LastLogin.After(u.LastLogin) && !u.LastLogin.IsZero()
}
// IsAdmin returns true if the user is an admin, false otherwise
func (u *User) IsAdmin() bool {
return u.Role == UserRoleAdmin
@@ -89,7 +82,6 @@ func (u *User) ToUserInfo(userData *idp.UserData) (*UserInfo, error) {
Status: string(UserStatusActive),
IsServiceUser: u.IsServiceUser,
IsBlocked: u.Blocked,
LastLogin: u.LastLogin,
}, nil
}
if userData.ID != u.Id {
@@ -110,7 +102,6 @@ func (u *User) ToUserInfo(userData *idp.UserData) (*UserInfo, error) {
Status: string(userStatus),
IsServiceUser: u.IsServiceUser,
IsBlocked: u.Blocked,
LastLogin: u.LastLogin,
}, nil
}
@@ -120,7 +111,9 @@ func (u *User) Copy() *User {
copy(autoGroups, u.AutoGroups)
pats := make(map[string]*PersonalAccessToken, len(u.PATs))
for k, v := range u.PATs {
pats[k] = v.Copy()
patCopy := new(PersonalAccessToken)
*patCopy = *v
pats[k] = patCopy
}
return &User{
Id: u.Id,
@@ -130,7 +123,6 @@ func (u *User) Copy() *User {
ServiceUserName: u.ServiceUserName,
PATs: pats,
Blocked: u.Blocked,
LastLogin: u.LastLogin,
}
}
@@ -194,7 +186,6 @@ func (am *DefaultAccountManager) createServiceUser(accountID string, initiatorUs
AutoGroups: newUser.AutoGroups,
Status: string(UserStatusActive),
IsServiceUser: true,
LastLogin: time.Time{},
}, nil
}
@@ -224,12 +215,6 @@ func (am *DefaultAccountManager) inviteNewUser(accountID, userID string, invite
return nil, status.Errorf(status.NotFound, "account %s doesn't exist", accountID)
}
// initiator is the one who is inviting the new user
initiatorUser, err := am.lookupUserInCache(userID, account)
if err != nil {
return nil, status.Errorf(status.NotFound, "user %s doesn't exist in IdP", userID)
}
// check if the user is already registered with this email => reject
user, err := am.lookupUserInCacheByEmail(invite.Email, accountID)
if err != nil {
@@ -249,7 +234,7 @@ func (am *DefaultAccountManager) inviteNewUser(accountID, userID string, invite
return nil, status.Errorf(status.UserAlreadyExists, "can't invite a user with an existing NetBird account")
}
idpUser, err := am.idpManager.CreateUser(invite.Email, invite.Name, accountID, initiatorUser.Email)
idpUser, err := am.idpManager.CreateUser(invite.Email, invite.Name, accountID)
if err != nil {
return nil, err
}
@@ -275,6 +260,7 @@ func (am *DefaultAccountManager) inviteNewUser(accountID, userID string, invite
am.storeEvent(userID, newUser.Id, accountID, activity.UserInvited, nil)
return newUser.ToUserInfo(idpUser)
}
// GetUser looks up a user by provided authorization claims.
@@ -289,21 +275,6 @@ func (am *DefaultAccountManager) GetUser(claims jwtclaims.AuthorizationClaims) (
if !ok {
return nil, status.Errorf(status.NotFound, "user not found")
}
// this code should be outside of the am.GetAccountFromToken(claims) because this method is called also by the gRPC
// server when user authenticates a device. And we need to separate the Dashboard login event from the Device login event.
unlock := am.Store.AcquireAccountLock(account.Id)
newLogin := user.LastDashboardLoginChanged(claims.LastLogin)
err = am.Store.SaveUserLastLogin(account.Id, claims.UserId, claims.LastLogin)
unlock()
if newLogin {
meta := map[string]any{"timestamp": claims.LastLogin}
am.storeEvent(claims.UserId, claims.UserId, account.Id, activity.DashboardLogin, meta)
if err != nil {
log.Errorf("failed saving user last login: %v", err)
}
}
return user, nil
}
@@ -629,24 +600,8 @@ func (am *DefaultAccountManager) SaveUser(accountID, initiatorUserID string, upd
}
}
if update.AutoGroups != nil && account.Settings.GroupsPropagationEnabled {
removedGroups := difference(oldUser.AutoGroups, update.AutoGroups)
// need force update all auto groups in any case they will not be dublicated
account.UserGroupsAddToPeers(oldUser.Id, update.AutoGroups...)
account.UserGroupsRemoveFromPeers(oldUser.Id, removedGroups...)
account.Network.IncSerial()
if err = am.Store.SaveAccount(account); err != nil {
return nil, err
}
if err := am.updateAccountPeers(account); err != nil {
log.Errorf("failed updating account peers while updating user %s", accountID)
}
} else {
if err = am.Store.SaveAccount(account); err != nil {
return nil, err
}
if err = am.Store.SaveAccount(account); err != nil {
return nil, err
}
defer func() {
@@ -674,6 +629,7 @@ func (am *DefaultAccountManager) SaveUser(accountID, initiatorUserID string, upd
} else {
log.Errorf("group %s not found while saving user activity event of account %s", g, account.Id)
}
}
for _, g := range addedGroups {
@@ -684,6 +640,7 @@ func (am *DefaultAccountManager) SaveUser(accountID, initiatorUserID string, upd
}
}
}
}()
if !isNil(am.idpManager) && !newUser.IsServiceUser {

View File

@@ -266,8 +266,7 @@ func TestUser_Copy(t *testing.T) {
LastUsed: time.Now(),
},
},
Blocked: false,
LastLogin: time.Now(),
Blocked: false,
}
err := validateStruct(user)

View File

@@ -24,21 +24,13 @@ download_release_binary() {
VERSION=$(get_latest_release)
BASE_URL="https://github.com/${OWNER}/${REPO}/releases/download"
BINARY_BASE_NAME="${VERSION#v}_${OS_TYPE}_${ARCH}.tar.gz"
# for Darwin, download the signed Netbird-UI
if [ "$OS_TYPE" = "darwin" ] && [ "$1" = "$UI_APP" ]; then
BINARY_BASE_NAME="${VERSION#v}_${OS_TYPE}_${ARCH}_signed.zip"
fi
if [ "$1" = "$UI_APP" ]; then
BINARY_NAME="$1-${OS_TYPE}_${BINARY_BASE_NAME}"
if [ "$OS_TYPE" = "darwin" ]; then
BINARY_NAME="$1_${BINARY_BASE_NAME}"
fi
else
BINARY_NAME="$1_${BINARY_BASE_NAME}"
fi
BINARY_NAME="$1_${BINARY_BASE_NAME}"
DOWNLOAD_URL="${BASE_URL}/${VERSION}/${BINARY_NAME}"
echo "Installing $1 from $DOWNLOAD_URL"
@@ -136,14 +128,6 @@ install_native_binaries() {
fi
}
check_use_bin_variable() {
if [ "${USE_BIN_INSTALL}-x" = "true-x" ]; then
echo "The installation will be performed using binary files"
return 0
fi
return 1
}
install_netbird() {
# Check if netbird CLI is installed
if [ -x "$(command -v netbird)" ]; then
@@ -186,10 +170,8 @@ install_netbird() {
echo "Netbird UI installation will be omitted as Linux does not run desktop environment"
fi
# Check the availability of a compatible package manager
if check_use_bin_variable; then
PACKAGE_MANAGER="bin"
elif [ -x "$(command -v apt)" ]; then
# Check the availability of a compactible package manager
if [ -x "$(command -v apt)" ]; then
PACKAGE_MANAGER="apt"
echo "The installation will be performed using apt package manager"
elif [ -x "$(command -v dnf)" ]; then
@@ -209,9 +191,7 @@ install_netbird() {
INSTALL_DIR="/usr/local/bin"
# Check the availability of a compatible package manager
if check_use_bin_variable; then
PACKAGE_MANAGER="bin"
elif [ -x "$(command -v brew)" ]; then
if [ -x "$(command -v brew)" ]; then
PACKAGE_MANAGER="brew"
echo "The installation will be performed using brew package manager"
fi

View File

@@ -1,9 +1,8 @@
package route
import (
"net/netip"
"github.com/netbirdio/netbird/management/server/status"
"net/netip"
)
// Windows has some limitation regarding metric size that differ from Unix-like systems.
@@ -84,7 +83,7 @@ func (r *Route) EventMeta() map[string]any {
// Copy copies a route object
func (r *Route) Copy() *Route {
route := &Route{
return &Route{
ID: r.ID,
Description: r.Description,
NetID: r.NetID,
@@ -94,10 +93,8 @@ func (r *Route) Copy() *Route {
Metric: r.Metric,
Masquerade: r.Masquerade,
Enabled: r.Enabled,
Groups: make([]string, len(r.Groups)),
Groups: r.Groups,
}
copy(route.Groups, r.Groups)
return route
}
// IsEqual compares one route with the other