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
43 changed files with 299 additions and 605 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>
@@ -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

@@ -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

@@ -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()

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,
}
}
@@ -629,96 +624,26 @@ func (a *Account) GetPeer(peerID string) *Peer {
return a.Peers[peerID]
}
// AddJWTGroups to account and to user autoassigned groups
func (a *Account) AddJWTGroups(userID string, groups []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
}
autoGroups := make(map[string]struct{})
for _, groupID := range user.AutoGroups {
autoGroups[groupID] = struct{}{}
}
var modified bool
var count int
for _, name := range groups {
group, ok := existedGroupsByName[name]
if !ok {
group = &Group{
ID: xid.New().String(),
if _, ok := existedGroups[name]; !ok {
id := xid.New().String()
a.Groups[id] = &Group{
ID: id,
Name: name,
Issued: GroupIssuedJWT,
}
a.Groups[group.ID] = group
modified = true
}
if _, ok := autoGroups[group.ID]; !ok {
if group.Issued == GroupIssuedJWT {
user.AutoGroups = append(user.AutoGroups, group.ID)
modified = true
}
count++
}
}
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
@@ -1365,13 +1290,11 @@ func (am *DefaultAccountManager) GetAccountFromToken(claims jwtclaims.Authorizat
log.Errorf("JWT claim %q is not a string: %v", account.Settings.JWTGroupsClaimName, item)
}
}
// if groups were added or modified, save the account
if account.AddJWTGroups(claims.UserId, groups) {
if account.Settings.GroupsPropagationEnabled {
if user, err := account.FindUser(claims.UserId); err == nil {
account.UserGroupsAddToPeers(claims.UserId, append(user.AutoGroups, groups...)...)
}
}
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)
}
@@ -1420,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

@@ -216,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) {
@@ -1930,120 +1931,6 @@ func TestAccount_GetNextPeerExpiration(t *testing.T) {
}
}
func TestAccount_AddJWTGroups(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.AddJWTGroups("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.AddJWTGroups("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.AddJWTGroups("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.AddJWTGroups("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

@@ -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
@@ -373,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

View File

@@ -129,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"`

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

@@ -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

@@ -260,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.
@@ -599,13 +600,6 @@ 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...)
}
if err = am.Store.SaveAccount(account); err != nil {
return nil, err
}
@@ -646,6 +640,7 @@ func (am *DefaultAccountManager) SaveUser(accountID, initiatorUserID string, upd
}
}
}
}()
if !isNil(am.idpManager) && !newUser.IsServiceUser {

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