mirror of
https://github.com/netbirdio/netbird.git
synced 2026-05-05 08:36:37 +00:00
Compare commits
12 Commits
ebpf-debug
...
v0.22.4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6c2b364966 | ||
|
|
0f0c7ec2ed | ||
|
|
2dec016201 | ||
|
|
06125acb8d | ||
|
|
a9b9b3fa0a | ||
|
|
cdf57275b7 | ||
|
|
e5e69b1f75 | ||
|
|
8eca83f3cb | ||
|
|
973316d194 | ||
|
|
a0a6ced148 | ||
|
|
0fc6c477a9 | ||
|
|
401a462398 |
91
README.md
91
README.md
@@ -1,6 +1,6 @@
|
|||||||
<p align="center">
|
<p align="center">
|
||||||
<strong>:hatching_chick: New Release! Peer expiration.</strong>
|
<strong>:hatching_chick: New Release! Self-hosting in under 5 min.</strong>
|
||||||
<a href="https://github.com/netbirdio/netbird/releases">
|
<a href="https://github.com/netbirdio/netbird#quickstart-with-self-hosted-netbird">
|
||||||
Learn more
|
Learn more
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
@@ -36,47 +36,62 @@
|
|||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
**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.**
|
**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.**
|
||||||
|
|
||||||
It requires zero configuration effort leaving behind the hassle of opening ports, complex firewall rules, VPN gateways, and so forth.
|
**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.
|
||||||
|
|
||||||
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.
|
**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.
|
||||||
|
|
||||||
**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
|
### Secure peer-to-peer VPN with SSO and MFA in minutes
|
||||||
|
|
||||||
https://user-images.githubusercontent.com/700848/197345890-2e2cded5-7b7a-436f-a444-94e80dd24f46.mov
|
https://user-images.githubusercontent.com/700848/197345890-2e2cded5-7b7a-436f-a444-94e80dd24f46.mov
|
||||||
|
|
||||||
**Note**: The `main` branch may be in an *unstable or even broken state* during development.
|
### Key features
|
||||||
For stable versions, see [releases](https://github.com/netbirdio/netbird/releases).
|
|
||||||
|
|
||||||
### Start using NetBird
|
| Connectivity | Management | Automation | Platforms |
|
||||||
- Hosted version: [https://app.netbird.io/](https://app.netbird.io/).
|
|-------------------------------------------------------------------|--------------------------------------------------------------------------|----------------------------------------------------------------------------|---------------------------------------|
|
||||||
- See our documentation for [Quickstart Guide](https://docs.netbird.io/how-to/getting-started).
|
| <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> |
|
||||||
- If you are looking to self-host NetBird, check our [Self-Hosting Guide](https://docs.netbird.io/selfhosted/selfhosted-guide).
|
| <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> |
|
||||||
- Step-by-step [Installation Guide](https://docs.netbird.io/how-to/getting-started#installation) for different platforms.
|
| <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> |
|
||||||
- Web UI [repository](https://github.com/netbirdio/dashboard).
|
| <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> |
|
||||||
- 5 min [demo video](https://youtu.be/Tu9tPsUWaY0) on YouTube.
|
| <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> | | |
|
||||||
|
|
||||||
|
|
||||||
|
### 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
|
### A bit on NetBird internals
|
||||||
- Every machine in the network runs [NetBird Agent (or Client)](client/) that manages WireGuard.
|
- 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).
|
- Every agent connects to [Management Service](management/) that holds network state, manages peer IPs, and distributes network updates to agents (peers).
|
||||||
@@ -88,18 +103,18 @@ For stable versions, see [releases](https://github.com/netbirdio/netbird/release
|
|||||||
[Coturn](https://github.com/coturn/coturn) is the one that has been successfully used for STUN and TURN in NetBird setups.
|
[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">
|
<p float="left" align="middle">
|
||||||
<img src="https://netbird.io/docs/img/architecture/high-level-dia.png" width="700"/>
|
<img src="https://docs.netbird.io/docs-static/img/architecture/high-level-dia.png" width="700"/>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
See a complete [architecture overview](https://docs.netbird.io/about-netbird/how-netbird-works#architecture) for details.
|
See a complete [architecture overview](https://docs.netbird.io/about-netbird/how-netbird-works#architecture) for details.
|
||||||
|
|
||||||
### Roadmap
|
|
||||||
- [Public Roadmap](https://github.com/netbirdio/netbird/projects/2)
|
|
||||||
|
|
||||||
### Community projects
|
### Community projects
|
||||||
- [NetBird on OpenWRT](https://github.com/messense/openwrt-netbird)
|
- [NetBird on OpenWRT](https://github.com/messense/openwrt-netbird)
|
||||||
- [NetBird installer script](https://github.com/physk/netbird-installer)
|
- [NetBird installer script](https://github.com/physk/netbird-installer)
|
||||||
|
|
||||||
|
**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
|
### 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.
|
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.
|
||||||
@@ -107,7 +122,7 @@ In November 2022, NetBird joined the [StartUpSecure program](https://www.forschu
|
|||||||

|

|
||||||
|
|
||||||
### Testimonials
|
### Testimonials
|
||||||
We use open-source technologies like [WireGuard®](https://www.wireguard.com/), [Pion ICE (WebRTC)](https://github.com/pion/ice), and [Coturn](https://github.com/coturn/coturn). We very much appreciate the work these guys are doing and we'd greatly appreciate if you could support them in any way (e.g. giving a star or a contribution).
|
We use open-source technologies like [WireGuard®](https://www.wireguard.com/), [Pion ICE (WebRTC)](https://github.com/pion/ice), [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).
|
||||||
|
|
||||||
### Legal
|
### Legal
|
||||||
_WireGuard_ and the _WireGuard_ logo are [registered trademarks](https://www.wireguard.com/trademark-policy/) of Jason A. Donenfeld.
|
_WireGuard_ and the _WireGuard_ logo are [registered trademarks](https://www.wireguard.com/trademark-policy/) of Jason A. Donenfeld.
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
FROM gcr.io/distroless/base:debug
|
FROM alpine:3
|
||||||
|
RUN apk add --no-cache ca-certificates iptables ip6tables
|
||||||
ENV NB_FOREGROUND_MODE=true
|
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"]
|
ENTRYPOINT [ "/go/bin/netbird","up"]
|
||||||
COPY netbird /go/bin/netbird
|
COPY netbird /go/bin/netbird
|
||||||
@@ -55,7 +55,6 @@ type Client struct {
|
|||||||
ctxCancelLock *sync.Mutex
|
ctxCancelLock *sync.Mutex
|
||||||
deviceName string
|
deviceName string
|
||||||
routeListener routemanager.RouteListener
|
routeListener routemanager.RouteListener
|
||||||
onHostDnsFn func([]string)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewClient instantiate a new Client
|
// NewClient instantiate a new Client
|
||||||
@@ -97,7 +96,30 @@ func (c *Client) Run(urlOpener URLOpener, dns *DNSList, dnsReadyListener DnsRead
|
|||||||
|
|
||||||
// todo do not throw error in case of cancelled context
|
// todo do not throw error in case of cancelled context
|
||||||
ctx = internal.CtxInitState(ctx)
|
ctx = internal.CtxInitState(ctx)
|
||||||
c.onHostDnsFn = func([]string) {}
|
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)
|
||||||
return internal.RunClientMobile(ctx, cfg, c.recorder, c.tunAdapter, c.iFaceDiscover, c.routeListener, dns.items, dnsReadyListener)
|
return internal.RunClientMobile(ctx, cfg, c.recorder, c.tunAdapter, c.iFaceDiscover, c.routeListener, dns.items, dnsReadyListener)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -179,8 +179,6 @@ func runClient(ctx context.Context, config *Config, statusRecorder *peer.Status,
|
|||||||
log.Print("Netbird engine started, my IP is: ", peerConfig.Address)
|
log.Print("Netbird engine started, my IP is: ", peerConfig.Address)
|
||||||
state.Set(StatusConnected)
|
state.Set(StatusConnected)
|
||||||
|
|
||||||
statusRecorder.ClientStart()
|
|
||||||
|
|
||||||
<-engineCtx.Done()
|
<-engineCtx.Done()
|
||||||
statusRecorder.ClientTeardown()
|
statusRecorder.ClientTeardown()
|
||||||
|
|
||||||
@@ -201,6 +199,7 @@ func runClient(ctx context.Context, config *Config, statusRecorder *peer.Status,
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
statusRecorder.ClientStart()
|
||||||
err = backoff.Retry(operation, backOff)
|
err = backoff.Retry(operation, backOff)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debugf("exiting client retry loop due to unrecoverable error: %s", err)
|
log.Debugf("exiting client retry loop due to unrecoverable error: %s", err)
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ type notifier struct {
|
|||||||
listener Listener
|
listener Listener
|
||||||
currentClientState bool
|
currentClientState bool
|
||||||
lastNotification int
|
lastNotification int
|
||||||
|
lastNumberOfPeers int
|
||||||
}
|
}
|
||||||
|
|
||||||
func newNotifier() *notifier {
|
func newNotifier() *notifier {
|
||||||
@@ -29,6 +30,7 @@ func (n *notifier) setListener(listener Listener) {
|
|||||||
|
|
||||||
n.serverStateLock.Lock()
|
n.serverStateLock.Lock()
|
||||||
n.notifyListener(listener, n.lastNotification)
|
n.notifyListener(listener, n.lastNotification)
|
||||||
|
listener.OnPeersListChanged(n.lastNumberOfPeers)
|
||||||
n.serverStateLock.Unlock()
|
n.serverStateLock.Unlock()
|
||||||
|
|
||||||
n.listener = listener
|
n.listener = listener
|
||||||
@@ -59,7 +61,7 @@ func (n *notifier) clientStart() {
|
|||||||
n.serverStateLock.Lock()
|
n.serverStateLock.Lock()
|
||||||
defer n.serverStateLock.Unlock()
|
defer n.serverStateLock.Unlock()
|
||||||
n.currentClientState = true
|
n.currentClientState = true
|
||||||
n.lastNotification = stateConnected
|
n.lastNotification = stateConnecting
|
||||||
n.notify(n.lastNotification)
|
n.notify(n.lastNotification)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,7 +114,7 @@ func (n *notifier) calculateState(managementConn, signalConn bool) int {
|
|||||||
return stateConnected
|
return stateConnected
|
||||||
}
|
}
|
||||||
|
|
||||||
if !managementConn && !signalConn {
|
if !managementConn && !signalConn && !n.currentClientState {
|
||||||
return stateDisconnected
|
return stateDisconnected
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,6 +126,7 @@ func (n *notifier) calculateState(managementConn, signalConn bool) int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (n *notifier) peerListChanged(numOfPeers int) {
|
func (n *notifier) peerListChanged(numOfPeers int) {
|
||||||
|
n.lastNumberOfPeers = numOfPeers
|
||||||
n.listenersLock.Lock()
|
n.listenersLock.Lock()
|
||||||
defer n.listenersLock.Unlock()
|
defer n.listenersLock.Unlock()
|
||||||
if n.listener == nil {
|
if n.listener == nil {
|
||||||
|
|||||||
@@ -353,9 +353,13 @@ func (d *Status) onConnectionChanged() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *Status) notifyPeerListChanged() {
|
func (d *Status) notifyPeerListChanged() {
|
||||||
d.notifier.peerListChanged(len(d.peers) + len(d.offlinePeers))
|
d.notifier.peerListChanged(d.numOfPeers())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Status) notifyAddressChanged() {
|
func (d *Status) notifyAddressChanged() {
|
||||||
d.notifier.localAddressChanged(d.localPeer.FQDN, d.localPeer.IP)
|
d.notifier.localAddressChanged(d.localPeer.FQDN, d.localPeer.IP)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *Status) numOfPeers() int {
|
||||||
|
return len(d.peers) + len(d.offlinePeers)
|
||||||
|
}
|
||||||
|
|||||||
@@ -27,14 +27,19 @@ func genKey(format string, input string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewFirewall if supported, returns an iptables manager, otherwise returns a nftables manager
|
// NewFirewall if supported, returns an iptables manager, otherwise returns a nftables manager
|
||||||
func NewFirewall(parentCTX context.Context) firewallManager {
|
func NewFirewall(parentCTX context.Context) (firewallManager, error) {
|
||||||
manager, err := newNFTablesManager(parentCTX)
|
manager, err := newNFTablesManager(parentCTX)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
log.Debugf("nftables firewall manager will be used")
|
log.Debugf("nftables firewall manager will be used")
|
||||||
return manager
|
return manager, nil
|
||||||
}
|
}
|
||||||
log.Debugf("fallback to iptables firewall manager: %s", err)
|
fMgr, err := newIptablesManager(parentCTX)
|
||||||
return 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
|
||||||
}
|
}
|
||||||
|
|
||||||
func getInPair(pair routerPair) routerPair {
|
func getInPair(pair routerPair) routerPair {
|
||||||
|
|||||||
@@ -3,24 +3,12 @@
|
|||||||
|
|
||||||
package routemanager
|
package routemanager
|
||||||
|
|
||||||
import "context"
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
type unimplementedFirewall struct{}
|
// NewFirewall returns a nil manager
|
||||||
|
func NewFirewall(context.Context) (firewallManager, error) {
|
||||||
func (unimplementedFirewall) RestoreOrCreateContainers() error {
|
return nil, fmt.Errorf("firewall not supported on this OS")
|
||||||
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{}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,14 +49,12 @@ type iptablesManager struct {
|
|||||||
mux sync.Mutex
|
mux sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func newIptablesManager(parentCtx context.Context) *iptablesManager {
|
func newIptablesManager(parentCtx context.Context) (*iptablesManager, error) {
|
||||||
ctx, cancel := context.WithCancel(parentCtx)
|
|
||||||
ipv4Client, err := iptables.NewWithProtocol(iptables.ProtocolIPv4)
|
ipv4Client, err := iptables.NewWithProtocol(iptables.ProtocolIPv4)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debugf("failed to initialize iptables for ipv4: %s", err)
|
return nil, err
|
||||||
} else if !isIptablesClientAvailable(ipv4Client) {
|
} else if !isIptablesClientAvailable(ipv4Client) {
|
||||||
log.Infof("iptables is missing for ipv4")
|
return nil, fmt.Errorf("iptables is missing for ipv4")
|
||||||
ipv4Client = nil
|
|
||||||
}
|
}
|
||||||
ipv6Client, err := iptables.NewWithProtocol(iptables.ProtocolIPv6)
|
ipv6Client, err := iptables.NewWithProtocol(iptables.ProtocolIPv6)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -66,13 +64,14 @@ func newIptablesManager(parentCtx context.Context) *iptablesManager {
|
|||||||
ipv6Client = nil
|
ipv6Client = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(parentCtx)
|
||||||
return &iptablesManager{
|
return &iptablesManager{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
stop: cancel,
|
stop: cancel,
|
||||||
ipv4Client: ipv4Client,
|
ipv4Client: ipv4Client,
|
||||||
ipv6Client: ipv6Client,
|
ipv6Client: ipv6Client,
|
||||||
rules: make(map[string]map[string][]string),
|
rules: make(map[string]map[string][]string),
|
||||||
}
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CleanRoutingRules cleans existing iptables resources that we created by the agent
|
// CleanRoutingRules cleans existing iptables resources that we created by the agent
|
||||||
@@ -395,6 +394,10 @@ func (i *iptablesManager) insertRoutingRule(keyFormat, table, chain, jump string
|
|||||||
ipVersion = ipv6
|
ipVersion = ipv6
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if iptablesClient == nil {
|
||||||
|
return fmt.Errorf("unable to insert iptables routing rules. Iptables client is not initialized")
|
||||||
|
}
|
||||||
|
|
||||||
ruleKey := genKey(keyFormat, pair.ID)
|
ruleKey := genKey(keyFormat, pair.ID)
|
||||||
rule := genRuleSpec(jump, ruleKey, pair.source, pair.destination)
|
rule := genRuleSpec(jump, ruleKey, pair.source, pair.destination)
|
||||||
existingRule, found := i.rules[ipVersion][ruleKey]
|
existingRule, found := i.rules[ipVersion][ruleKey]
|
||||||
@@ -459,6 +462,10 @@ func (i *iptablesManager) removeRoutingRule(keyFormat, table, chain string, pair
|
|||||||
ipVersion = ipv6
|
ipVersion = ipv6
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if iptablesClient == nil {
|
||||||
|
return fmt.Errorf("unable to remove iptables routing rules. Iptables client is not initialized")
|
||||||
|
}
|
||||||
|
|
||||||
ruleKey := genKey(keyFormat, pair.ID)
|
ruleKey := genKey(keyFormat, pair.ID)
|
||||||
existingRule, found := i.rules[ipVersion][ruleKey]
|
existingRule, found := i.rules[ipVersion][ruleKey]
|
||||||
if found {
|
if found {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ func TestIptablesManager_RestoreOrCreateContainers(t *testing.T) {
|
|||||||
t.SkipNow()
|
t.SkipNow()
|
||||||
}
|
}
|
||||||
|
|
||||||
manager := newIptablesManager(context.TODO())
|
manager, _ := newIptablesManager(context.TODO())
|
||||||
|
|
||||||
defer manager.CleanRoutingRules()
|
defer manager.CleanRoutingRules()
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ type DefaultManager struct {
|
|||||||
stop context.CancelFunc
|
stop context.CancelFunc
|
||||||
mux sync.Mutex
|
mux sync.Mutex
|
||||||
clientNetworks map[string]*clientNetwork
|
clientNetworks map[string]*clientNetwork
|
||||||
serverRouter *serverRouter
|
serverRouter serverRouter
|
||||||
statusRecorder *peer.Status
|
statusRecorder *peer.Status
|
||||||
wgInterface *iface.WGIface
|
wgInterface *iface.WGIface
|
||||||
pubKey string
|
pubKey string
|
||||||
@@ -36,13 +36,17 @@ type DefaultManager struct {
|
|||||||
|
|
||||||
// NewManager returns a new route manager
|
// NewManager returns a new route manager
|
||||||
func NewManager(ctx context.Context, pubKey string, wgInterface *iface.WGIface, statusRecorder *peer.Status, initialRoutes []*route.Route) *DefaultManager {
|
func NewManager(ctx context.Context, pubKey string, wgInterface *iface.WGIface, statusRecorder *peer.Status, initialRoutes []*route.Route) *DefaultManager {
|
||||||
mCTX, cancel := context.WithCancel(ctx)
|
serverRouter, err := newServerRouter(ctx, wgInterface)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("server router is not supported: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
mCTX, cancel := context.WithCancel(ctx)
|
||||||
dm := &DefaultManager{
|
dm := &DefaultManager{
|
||||||
ctx: mCTX,
|
ctx: mCTX,
|
||||||
stop: cancel,
|
stop: cancel,
|
||||||
clientNetworks: make(map[string]*clientNetwork),
|
clientNetworks: make(map[string]*clientNetwork),
|
||||||
serverRouter: newServerRouter(ctx, wgInterface),
|
serverRouter: serverRouter,
|
||||||
statusRecorder: statusRecorder,
|
statusRecorder: statusRecorder,
|
||||||
wgInterface: wgInterface,
|
wgInterface: wgInterface,
|
||||||
pubKey: pubKey,
|
pubKey: pubKey,
|
||||||
@@ -59,7 +63,9 @@ func NewManager(ctx context.Context, pubKey string, wgInterface *iface.WGIface,
|
|||||||
// Stop stops the manager watchers and clean firewall rules
|
// Stop stops the manager watchers and clean firewall rules
|
||||||
func (m *DefaultManager) Stop() {
|
func (m *DefaultManager) Stop() {
|
||||||
m.stop()
|
m.stop()
|
||||||
|
if m.serverRouter != nil {
|
||||||
m.serverRouter.cleanUp()
|
m.serverRouter.cleanUp()
|
||||||
|
}
|
||||||
m.ctx = nil
|
m.ctx = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,10 +83,13 @@ func (m *DefaultManager) UpdateRoutes(updateSerial uint64, newRoutes []*route.Ro
|
|||||||
|
|
||||||
m.updateClientNetworks(updateSerial, newClientRoutesIDMap)
|
m.updateClientNetworks(updateSerial, newClientRoutesIDMap)
|
||||||
m.notifier.onNewRoutes(newClientRoutesIDMap)
|
m.notifier.onNewRoutes(newClientRoutesIDMap)
|
||||||
|
|
||||||
|
if m.serverRouter != nil {
|
||||||
err := m.serverRouter.updateRoutes(newServerRoutesMap)
|
err := m.serverRouter.updateRoutes(newServerRoutesMap)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ func TestManagerUpdateRoutes(t *testing.T) {
|
|||||||
inputInitRoutes []*route.Route
|
inputInitRoutes []*route.Route
|
||||||
inputRoutes []*route.Route
|
inputRoutes []*route.Route
|
||||||
inputSerial uint64
|
inputSerial uint64
|
||||||
shouldCheckServerRoutes bool
|
|
||||||
serverRoutesExpected int
|
serverRoutesExpected int
|
||||||
clientNetworkWatchersExpected int
|
clientNetworkWatchersExpected int
|
||||||
}{
|
}{
|
||||||
@@ -87,7 +86,6 @@ func TestManagerUpdateRoutes(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
inputSerial: 1,
|
inputSerial: 1,
|
||||||
shouldCheckServerRoutes: runtime.GOOS == "linux",
|
|
||||||
serverRoutesExpected: 2,
|
serverRoutesExpected: 2,
|
||||||
clientNetworkWatchersExpected: 0,
|
clientNetworkWatchersExpected: 0,
|
||||||
},
|
},
|
||||||
@@ -116,7 +114,6 @@ func TestManagerUpdateRoutes(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
inputSerial: 1,
|
inputSerial: 1,
|
||||||
shouldCheckServerRoutes: runtime.GOOS == "linux",
|
|
||||||
serverRoutesExpected: 1,
|
serverRoutesExpected: 1,
|
||||||
clientNetworkWatchersExpected: 1,
|
clientNetworkWatchersExpected: 1,
|
||||||
},
|
},
|
||||||
@@ -174,25 +171,6 @@ func TestManagerUpdateRoutes(t *testing.T) {
|
|||||||
inputSerial: 1,
|
inputSerial: 1,
|
||||||
clientNetworkWatchersExpected: 0,
|
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",
|
name: "Remove 1 Client Route",
|
||||||
inputInitRoutes: []*route.Route{
|
inputInitRoutes: []*route.Route{
|
||||||
@@ -335,7 +313,6 @@ func TestManagerUpdateRoutes(t *testing.T) {
|
|||||||
},
|
},
|
||||||
inputRoutes: []*route.Route{},
|
inputRoutes: []*route.Route{},
|
||||||
inputSerial: 1,
|
inputSerial: 1,
|
||||||
shouldCheckServerRoutes: true,
|
|
||||||
serverRoutesExpected: 0,
|
serverRoutesExpected: 0,
|
||||||
clientNetworkWatchersExpected: 0,
|
clientNetworkWatchersExpected: 0,
|
||||||
},
|
},
|
||||||
@@ -384,7 +361,6 @@ func TestManagerUpdateRoutes(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
inputSerial: 1,
|
inputSerial: 1,
|
||||||
shouldCheckServerRoutes: runtime.GOOS == "linux",
|
|
||||||
serverRoutesExpected: 2,
|
serverRoutesExpected: 2,
|
||||||
clientNetworkWatchersExpected: 1,
|
clientNetworkWatchersExpected: 1,
|
||||||
},
|
},
|
||||||
@@ -419,8 +395,9 @@ func TestManagerUpdateRoutes(t *testing.T) {
|
|||||||
|
|
||||||
require.Len(t, routeManager.clientNetworks, testCase.clientNetworkWatchersExpected, "client networks size should match")
|
require.Len(t, routeManager.clientNetworks, testCase.clientNetworkWatchersExpected, "client networks size should match")
|
||||||
|
|
||||||
if testCase.shouldCheckServerRoutes {
|
if runtime.GOOS == "linux" {
|
||||||
require.Len(t, routeManager.serverRouter.routes, testCase.serverRoutesExpected, "server networks size should match")
|
sr := routeManager.serverRouter.(*defaultServerRouter)
|
||||||
|
require.Len(t, sr.routes, testCase.serverRoutesExpected, "server networks size should match")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
9
client/internal/routemanager/server.go
Normal file
9
client/internal/routemanager/server.go
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package routemanager
|
||||||
|
|
||||||
|
import "github.com/netbirdio/netbird/route"
|
||||||
|
|
||||||
|
type serverRouter interface {
|
||||||
|
updateRoutes(map[string]*route.Route) error
|
||||||
|
removeFromServerNetwork(*route.Route) error
|
||||||
|
cleanUp()
|
||||||
|
}
|
||||||
@@ -2,20 +2,11 @@ package routemanager
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/iface"
|
"github.com/netbirdio/netbird/iface"
|
||||||
"github.com/netbirdio/netbird/route"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type serverRouter struct {
|
func newServerRouter(context.Context, *iface.WGIface) (serverRouter, error) {
|
||||||
|
return nil, fmt.Errorf("server route not supported on this os")
|
||||||
}
|
}
|
||||||
|
|
||||||
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() {}
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import (
|
|||||||
"github.com/netbirdio/netbird/route"
|
"github.com/netbirdio/netbird/route"
|
||||||
)
|
)
|
||||||
|
|
||||||
type serverRouter struct {
|
type defaultServerRouter struct {
|
||||||
mux sync.Mutex
|
mux sync.Mutex
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
routes map[string]*route.Route
|
routes map[string]*route.Route
|
||||||
@@ -21,16 +21,21 @@ type serverRouter struct {
|
|||||||
wgInterface *iface.WGIface
|
wgInterface *iface.WGIface
|
||||||
}
|
}
|
||||||
|
|
||||||
func newServerRouter(ctx context.Context, wgInterface *iface.WGIface) *serverRouter {
|
func newServerRouter(ctx context.Context, wgInterface *iface.WGIface) (serverRouter, error) {
|
||||||
return &serverRouter{
|
firewall, err := NewFirewall(ctx)
|
||||||
ctx: ctx,
|
if err != nil {
|
||||||
routes: make(map[string]*route.Route),
|
return nil, err
|
||||||
firewall: NewFirewall(ctx),
|
|
||||||
wgInterface: wgInterface,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *serverRouter) updateRoutes(routesMap map[string]*route.Route) error {
|
return &defaultServerRouter{
|
||||||
|
ctx: ctx,
|
||||||
|
routes: make(map[string]*route.Route),
|
||||||
|
firewall: firewall,
|
||||||
|
wgInterface: wgInterface,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *defaultServerRouter) updateRoutes(routesMap map[string]*route.Route) error {
|
||||||
serverRoutesToRemove := make([]string, 0)
|
serverRoutesToRemove := make([]string, 0)
|
||||||
|
|
||||||
if len(routesMap) > 0 {
|
if len(routesMap) > 0 {
|
||||||
@@ -81,7 +86,7 @@ func (m *serverRouter) updateRoutes(routesMap map[string]*route.Route) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *serverRouter) removeFromServerNetwork(route *route.Route) error {
|
func (m *defaultServerRouter) removeFromServerNetwork(route *route.Route) error {
|
||||||
select {
|
select {
|
||||||
case <-m.ctx.Done():
|
case <-m.ctx.Done():
|
||||||
log.Infof("not removing from server network because context is done")
|
log.Infof("not removing from server network because context is done")
|
||||||
@@ -98,7 +103,7 @@ func (m *serverRouter) removeFromServerNetwork(route *route.Route) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *serverRouter) addToServerNetwork(route *route.Route) error {
|
func (m *defaultServerRouter) addToServerNetwork(route *route.Route) error {
|
||||||
select {
|
select {
|
||||||
case <-m.ctx.Done():
|
case <-m.ctx.Done():
|
||||||
log.Infof("not adding to server network because context is done")
|
log.Infof("not adding to server network because context is done")
|
||||||
@@ -115,6 +120,6 @@ func (m *serverRouter) addToServerNetwork(route *route.Route) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *serverRouter) cleanUp() {
|
func (m *defaultServerRouter) cleanUp() {
|
||||||
m.firewall.CleanRoutingRules()
|
m.firewall.CleanRoutingRules()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ func InterfaceFilter(disallowList []string) func(string) bool {
|
|||||||
|
|
||||||
for _, s := range disallowList {
|
for _, s := range disallowList {
|
||||||
if strings.HasPrefix(iFace, s) {
|
if strings.HasPrefix(iFace, s) {
|
||||||
log.Debugf("ignoring interface %s - it is not allowed", iFace)
|
log.Tracef("ignoring interface %s - it is not allowed", iFace)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,14 +54,14 @@ type bpfSpecs struct {
|
|||||||
//
|
//
|
||||||
// It can be passed ebpf.CollectionSpec.Assign.
|
// It can be passed ebpf.CollectionSpec.Assign.
|
||||||
type bpfProgramSpecs struct {
|
type bpfProgramSpecs struct {
|
||||||
XdpProgFunc *ebpf.ProgramSpec `ebpf:"xdp_prog_func"`
|
NbWgProxy *ebpf.ProgramSpec `ebpf:"nb_wg_proxy"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// bpfMapSpecs contains maps before they are loaded into the kernel.
|
// bpfMapSpecs contains maps before they are loaded into the kernel.
|
||||||
//
|
//
|
||||||
// It can be passed ebpf.CollectionSpec.Assign.
|
// It can be passed ebpf.CollectionSpec.Assign.
|
||||||
type bpfMapSpecs struct {
|
type bpfMapSpecs struct {
|
||||||
XdpPortMap *ebpf.MapSpec `ebpf:"xdp_port_map"`
|
NbWgProxySettingsMap *ebpf.MapSpec `ebpf:"nb_wg_proxy_settings_map"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// bpfObjects contains all objects after they have been loaded into the kernel.
|
// 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.
|
// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||||
type bpfMaps struct {
|
type bpfMaps struct {
|
||||||
XdpPortMap *ebpf.Map `ebpf:"xdp_port_map"`
|
NbWgProxySettingsMap *ebpf.Map `ebpf:"nb_wg_proxy_settings_map"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *bpfMaps) Close() error {
|
func (m *bpfMaps) Close() error {
|
||||||
return _BpfClose(
|
return _BpfClose(
|
||||||
m.XdpPortMap,
|
m.NbWgProxySettingsMap,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,12 +96,12 @@ func (m *bpfMaps) Close() error {
|
|||||||
//
|
//
|
||||||
// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign.
|
// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||||
type bpfPrograms struct {
|
type bpfPrograms struct {
|
||||||
XdpProgFunc *ebpf.Program `ebpf:"xdp_prog_func"`
|
NbWgProxy *ebpf.Program `ebpf:"nb_wg_proxy"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *bpfPrograms) Close() error {
|
func (p *bpfPrograms) Close() error {
|
||||||
return _BpfClose(
|
return _BpfClose(
|
||||||
p.XdpProgFunc,
|
p.NbWgProxy,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
@@ -54,14 +54,14 @@ type bpfSpecs struct {
|
|||||||
//
|
//
|
||||||
// It can be passed ebpf.CollectionSpec.Assign.
|
// It can be passed ebpf.CollectionSpec.Assign.
|
||||||
type bpfProgramSpecs struct {
|
type bpfProgramSpecs struct {
|
||||||
XdpProgFunc *ebpf.ProgramSpec `ebpf:"xdp_prog_func"`
|
NbWgProxy *ebpf.ProgramSpec `ebpf:"nb_wg_proxy"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// bpfMapSpecs contains maps before they are loaded into the kernel.
|
// bpfMapSpecs contains maps before they are loaded into the kernel.
|
||||||
//
|
//
|
||||||
// It can be passed ebpf.CollectionSpec.Assign.
|
// It can be passed ebpf.CollectionSpec.Assign.
|
||||||
type bpfMapSpecs struct {
|
type bpfMapSpecs struct {
|
||||||
XdpPortMap *ebpf.MapSpec `ebpf:"xdp_port_map"`
|
NbWgProxySettingsMap *ebpf.MapSpec `ebpf:"nb_wg_proxy_settings_map"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// bpfObjects contains all objects after they have been loaded into the kernel.
|
// 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.
|
// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||||
type bpfMaps struct {
|
type bpfMaps struct {
|
||||||
XdpPortMap *ebpf.Map `ebpf:"xdp_port_map"`
|
NbWgProxySettingsMap *ebpf.Map `ebpf:"nb_wg_proxy_settings_map"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *bpfMaps) Close() error {
|
func (m *bpfMaps) Close() error {
|
||||||
return _BpfClose(
|
return _BpfClose(
|
||||||
m.XdpPortMap,
|
m.NbWgProxySettingsMap,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,12 +96,12 @@ func (m *bpfMaps) Close() error {
|
|||||||
//
|
//
|
||||||
// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign.
|
// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign.
|
||||||
type bpfPrograms struct {
|
type bpfPrograms struct {
|
||||||
XdpProgFunc *ebpf.Program `ebpf:"xdp_prog_func"`
|
NbWgProxy *ebpf.Program `ebpf:"nb_wg_proxy"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *bpfPrograms) Close() error {
|
func (p *bpfPrograms) Close() error {
|
||||||
return _BpfClose(
|
return _BpfClose(
|
||||||
p.XdpProgFunc,
|
p.NbWgProxy,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
@@ -50,22 +50,22 @@ func (l *EBPF) Load(proxyPort, wgPort int) error {
|
|||||||
_ = objs.Close()
|
_ = objs.Close()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
err = objs.XdpPortMap.Put(mapKeyProxyPort, uint16(proxyPort))
|
err = objs.NbWgProxySettingsMap.Put(mapKeyProxyPort, uint16(proxyPort))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = objs.XdpPortMap.Put(mapKeyWgPort, uint16(wgPort))
|
err = objs.NbWgProxySettingsMap.Put(mapKeyWgPort, uint16(wgPort))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
_ = objs.XdpPortMap.Close()
|
_ = objs.NbWgProxySettingsMap.Close()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
l.link, err = link.AttachXDP(link.XDPOptions{
|
l.link, err = link.AttachXDP(link.XDPOptions{
|
||||||
Program: objs.XdpProgFunc,
|
Program: objs.NbWgProxy,
|
||||||
Interface: ifce.Index,
|
Interface: ifce.Index,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -75,7 +75,7 @@ func (l *EBPF) Load(proxyPort, wgPort int) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Free free ebpf program
|
// Free ebpf program
|
||||||
func (l *EBPF) Free() error {
|
func (l *EBPF) Free() error {
|
||||||
if l.link != nil {
|
if l.link != nil {
|
||||||
return l.link.Close()
|
return l.link.Close()
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
const __u32 map_key_proxy_port = 0;
|
const __u32 map_key_proxy_port = 0;
|
||||||
const __u32 map_key_wg_port = 1;
|
const __u32 map_key_wg_port = 1;
|
||||||
|
|
||||||
struct bpf_map_def SEC("maps") xdp_port_map = {
|
struct bpf_map_def SEC("maps") nb_wg_proxy_settings_map = {
|
||||||
.type = BPF_MAP_TYPE_ARRAY,
|
.type = BPF_MAP_TYPE_ARRAY,
|
||||||
.key_size = sizeof(__u32),
|
.key_size = sizeof(__u32),
|
||||||
.value_size = sizeof(__u16),
|
.value_size = sizeof(__u16),
|
||||||
@@ -27,14 +27,14 @@ __u16 wg_port = 0;
|
|||||||
|
|
||||||
bool read_port_settings() {
|
bool read_port_settings() {
|
||||||
__u16 *value;
|
__u16 *value;
|
||||||
value = bpf_map_lookup_elem(&xdp_port_map, &map_key_proxy_port);
|
value = bpf_map_lookup_elem(&nb_wg_proxy_settings_map, &map_key_proxy_port);
|
||||||
if(!value) {
|
if(!value) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
proxy_port = *value;
|
proxy_port = *value;
|
||||||
|
|
||||||
value = bpf_map_lookup_elem(&xdp_port_map, &map_key_wg_port);
|
value = bpf_map_lookup_elem(&nb_wg_proxy_settings_map, &map_key_wg_port);
|
||||||
if(!value) {
|
if(!value) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -44,7 +44,7 @@ bool read_port_settings() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SEC("xdp")
|
SEC("xdp")
|
||||||
int xdp_prog_func(struct xdp_md *ctx) {
|
int nb_wg_proxy(struct xdp_md *ctx) {
|
||||||
if(proxy_port == 0 || wg_port == 0) {
|
if(proxy_port == 0 || wg_port == 0) {
|
||||||
if(!read_port_settings()){
|
if(!read_port_settings()){
|
||||||
return XDP_PASS;
|
return XDP_PASS;
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ func (w *Factory) GetProxy() Proxy {
|
|||||||
|
|
||||||
func (w *Factory) Free() error {
|
func (w *Factory) Free() error {
|
||||||
if w.ebpfProxy != nil {
|
if w.ebpfProxy != nil {
|
||||||
return w.ebpfProxy.CloseConn()
|
return w.ebpfProxy.Free()
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -104,6 +104,7 @@ func (p *WGEBPFProxy) CloseConn() error {
|
|||||||
|
|
||||||
// Free resources
|
// Free resources
|
||||||
func (p *WGEBPFProxy) Free() error {
|
func (p *WGEBPFProxy) Free() error {
|
||||||
|
log.Debugf("free up ebpf wg proxy")
|
||||||
var err1, err2, err3 error
|
var err1, err2, err3 error
|
||||||
if p.conn != nil {
|
if p.conn != nil {
|
||||||
err1 = p.conn.Close()
|
err1 = p.conn.Close()
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ type WGUserSpaceProxy struct {
|
|||||||
|
|
||||||
// NewWGUserSpaceProxy instantiate a user space WireGuard proxy
|
// NewWGUserSpaceProxy instantiate a user space WireGuard proxy
|
||||||
func NewWGUserSpaceProxy(wgPort int) *WGUserSpaceProxy {
|
func NewWGUserSpaceProxy(wgPort int) *WGUserSpaceProxy {
|
||||||
|
log.Debugf("instantiate new userspace proxy")
|
||||||
p := &WGUserSpaceProxy{
|
p := &WGUserSpaceProxy{
|
||||||
localWGListenPort: wgPort,
|
localWGListenPort: wgPort,
|
||||||
}
|
}
|
||||||
|
|||||||
2
go.mod
2
go.mod
@@ -31,7 +31,7 @@ require (
|
|||||||
fyne.io/fyne/v2 v2.1.4
|
fyne.io/fyne/v2 v2.1.4
|
||||||
github.com/c-robinson/iplib v1.0.3
|
github.com/c-robinson/iplib v1.0.3
|
||||||
github.com/cilium/ebpf v0.10.0
|
github.com/cilium/ebpf v0.10.0
|
||||||
github.com/coreos/go-iptables v0.6.0
|
github.com/coreos/go-iptables v0.7.0
|
||||||
github.com/creack/pty v1.1.18
|
github.com/creack/pty v1.1.18
|
||||||
github.com/eko/gocache/v3 v3.1.1
|
github.com/eko/gocache/v3 v3.1.1
|
||||||
github.com/getlantern/systray v1.2.1
|
github.com/getlantern/systray v1.2.1
|
||||||
|
|||||||
4
go.sum
4
go.sum
@@ -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/coocood/freecache v1.2.1 h1:/v1CqMq45NFH9mp/Pt142reundeBM0dVUD3osQBeu/U=
|
||||||
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
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/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||||
github.com/coreos/go-iptables v0.6.0 h1:is9qnZMPYjLd8LYqmm/qlE+wwEgJIkTYdhV3rfZo4jk=
|
github.com/coreos/go-iptables v0.7.0 h1:XWM3V+MPRr5/q51NuWSgU0fqMad64Zyxs8ZUoMsamr8=
|
||||||
github.com/coreos/go-iptables v0.6.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
|
github.com/coreos/go-iptables v0.7.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
|
||||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
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 v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||||
github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=
|
github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=
|
||||||
|
|||||||
@@ -395,7 +395,7 @@ check_nb_domain() {
|
|||||||
read_nb_domain() {
|
read_nb_domain() {
|
||||||
READ_NETBIRD_DOMAIN=""
|
READ_NETBIRD_DOMAIN=""
|
||||||
echo -n "Enter the domain you want to use for NetBird (e.g. netbird.my-domain.com): " > /dev/stderr
|
echo -n "Enter the domain you want to use for NetBird (e.g. netbird.my-domain.com): " > /dev/stderr
|
||||||
read -r READ_NETBIRD_DOMAIN
|
read -r READ_NETBIRD_DOMAIN < /dev/tty
|
||||||
if ! check_nb_domain "$READ_NETBIRD_DOMAIN"; then
|
if ! check_nb_domain "$READ_NETBIRD_DOMAIN"; then
|
||||||
read_nb_domain
|
read_nb_domain
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -139,10 +139,14 @@ type DefaultAccountManager struct {
|
|||||||
type Settings struct {
|
type Settings struct {
|
||||||
// PeerLoginExpirationEnabled globally enables or disables peer login expiration
|
// PeerLoginExpirationEnabled globally enables or disables peer login expiration
|
||||||
PeerLoginExpirationEnabled bool
|
PeerLoginExpirationEnabled bool
|
||||||
|
|
||||||
// PeerLoginExpiration is a setting that indicates when peer login expires.
|
// PeerLoginExpiration is a setting that indicates when peer login expires.
|
||||||
// Applies to all peers that have Peer.LoginExpirationEnabled set to true.
|
// Applies to all peers that have Peer.LoginExpirationEnabled set to true.
|
||||||
PeerLoginExpiration time.Duration
|
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
|
// JWTGroupsEnabled allows extract groups from JWT claim, which name defined in the JWTGroupsClaimName
|
||||||
// and add it to account groups.
|
// and add it to account groups.
|
||||||
JWTGroupsEnabled bool
|
JWTGroupsEnabled bool
|
||||||
@@ -158,6 +162,7 @@ func (s *Settings) Copy() *Settings {
|
|||||||
PeerLoginExpiration: s.PeerLoginExpiration,
|
PeerLoginExpiration: s.PeerLoginExpiration,
|
||||||
JWTGroupsEnabled: s.JWTGroupsEnabled,
|
JWTGroupsEnabled: s.JWTGroupsEnabled,
|
||||||
JWTGroupsClaimName: s.JWTGroupsClaimName,
|
JWTGroupsClaimName: s.JWTGroupsClaimName,
|
||||||
|
GroupsPropagationEnabled: s.GroupsPropagationEnabled,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -624,26 +629,96 @@ func (a *Account) GetPeer(peerID string) *Peer {
|
|||||||
return a.Peers[peerID]
|
return a.Peers[peerID]
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddJWTGroups to existed groups if they does not exists
|
// AddJWTGroups to account and to user autoassigned groups
|
||||||
func (a *Account) AddJWTGroups(groups []string) (int, error) {
|
func (a *Account) AddJWTGroups(userID string, groups []string) bool {
|
||||||
existedGroups := make(map[string]*Group)
|
user, ok := a.Users[userID]
|
||||||
for _, g := range a.Groups {
|
if !ok {
|
||||||
existedGroups[g.Name] = g
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
var count int
|
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
|
||||||
for _, name := range groups {
|
for _, name := range groups {
|
||||||
if _, ok := existedGroups[name]; !ok {
|
group, ok := existedGroupsByName[name]
|
||||||
id := xid.New().String()
|
if !ok {
|
||||||
a.Groups[id] = &Group{
|
group = &Group{
|
||||||
ID: id,
|
ID: xid.New().String(),
|
||||||
Name: name,
|
Name: name,
|
||||||
Issued: GroupIssuedJWT,
|
Issued: GroupIssuedJWT,
|
||||||
}
|
}
|
||||||
count++
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return count, nil
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// BuildManager creates a new DefaultAccountManager with a provided Store
|
// BuildManager creates a new DefaultAccountManager with a provided Store
|
||||||
@@ -1290,11 +1365,13 @@ func (am *DefaultAccountManager) GetAccountFromToken(claims jwtclaims.Authorizat
|
|||||||
log.Errorf("JWT claim %q is not a string: %v", account.Settings.JWTGroupsClaimName, item)
|
log.Errorf("JWT claim %q is not a string: %v", account.Settings.JWTGroupsClaimName, item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
n, err := account.AddJWTGroups(groups)
|
// if groups were added or modified, save the account
|
||||||
if err != nil {
|
if account.AddJWTGroups(claims.UserId, groups) {
|
||||||
log.Errorf("failed to add JWT groups: %v", err)
|
if account.Settings.GroupsPropagationEnabled {
|
||||||
|
if user, err := account.FindUser(claims.UserId); err == nil {
|
||||||
|
account.UserGroupsAddToPeers(claims.UserId, append(user.AutoGroups, groups...)...)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if n > 0 {
|
|
||||||
if err := am.Store.SaveAccount(account); err != nil {
|
if err := am.Store.SaveAccount(account); err != nil {
|
||||||
log.Errorf("failed to save account: %v", err)
|
log.Errorf("failed to save account: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -216,7 +216,6 @@ func TestAccount_GetPeerNetworkMap(t *testing.T) {
|
|||||||
assert.Len(t, networkMap.Peers, len(testCase.expectedPeers))
|
assert.Len(t, networkMap.Peers, len(testCase.expectedPeers))
|
||||||
assert.Len(t, networkMap.OfflinePeers, len(testCase.expectedOfflinePeers))
|
assert.Len(t, networkMap.OfflinePeers, len(testCase.expectedOfflinePeers))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewAccount(t *testing.T) {
|
func TestNewAccount(t *testing.T) {
|
||||||
@@ -1931,6 +1930,120 @@ 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) {
|
func createManager(t *testing.T) (*DefaultAccountManager, error) {
|
||||||
store, err := createStore(t)
|
store, err := createStore(t)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -80,12 +80,14 @@ func (h *AccountsHandler) UpdateAccount(w http.ResponseWriter, r *http.Request)
|
|||||||
if req.Settings.JwtGroupsEnabled != nil {
|
if req.Settings.JwtGroupsEnabled != nil {
|
||||||
settings.JWTGroupsEnabled = *req.Settings.JwtGroupsEnabled
|
settings.JWTGroupsEnabled = *req.Settings.JwtGroupsEnabled
|
||||||
}
|
}
|
||||||
|
if req.Settings.GroupsPropagationEnabled != nil {
|
||||||
|
settings.GroupsPropagationEnabled = *req.Settings.GroupsPropagationEnabled
|
||||||
|
}
|
||||||
if req.Settings.JwtGroupsClaimName != nil {
|
if req.Settings.JwtGroupsClaimName != nil {
|
||||||
settings.JWTGroupsClaimName = *req.Settings.JwtGroupsClaimName
|
settings.JWTGroupsClaimName = *req.Settings.JwtGroupsClaimName
|
||||||
}
|
}
|
||||||
|
|
||||||
updatedAccount, err := h.accountManager.UpdateAccountSettings(accountID, user.Id, settings)
|
updatedAccount, err := h.accountManager.UpdateAccountSettings(accountID, user.Id, settings)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.WriteError(err, w)
|
util.WriteError(err, w)
|
||||||
return
|
return
|
||||||
@@ -102,6 +104,7 @@ func toAccountResponse(account *server.Account) *api.Account {
|
|||||||
Settings: api.AccountSettings{
|
Settings: api.AccountSettings{
|
||||||
PeerLoginExpiration: int(account.Settings.PeerLoginExpiration.Seconds()),
|
PeerLoginExpiration: int(account.Settings.PeerLoginExpiration.Seconds()),
|
||||||
PeerLoginExpirationEnabled: account.Settings.PeerLoginExpirationEnabled,
|
PeerLoginExpirationEnabled: account.Settings.PeerLoginExpirationEnabled,
|
||||||
|
GroupsPropagationEnabled: &account.Settings.GroupsPropagationEnabled,
|
||||||
JwtGroupsEnabled: &account.Settings.JWTGroupsEnabled,
|
JwtGroupsEnabled: &account.Settings.JWTGroupsEnabled,
|
||||||
JwtGroupsClaimName: &account.Settings.JWTGroupsClaimName,
|
JwtGroupsClaimName: &account.Settings.JWTGroupsClaimName,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ func initAccountsTestData(account *server.Account, admin *server.User) *Accounts
|
|||||||
accCopy := account.Copy()
|
accCopy := account.Copy()
|
||||||
accCopy.UpdateSettings(newSettings)
|
accCopy.UpdateSettings(newSettings)
|
||||||
return accCopy, nil
|
return accCopy, nil
|
||||||
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
claimsExtractor: jwtclaims.NewClaimsExtractor(
|
claimsExtractor: jwtclaims.NewClaimsExtractor(
|
||||||
@@ -54,7 +53,6 @@ func initAccountsTestData(account *server.Account, admin *server.User) *Accounts
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestAccounts_AccountsHandler(t *testing.T) {
|
func TestAccounts_AccountsHandler(t *testing.T) {
|
||||||
|
|
||||||
accountID := "test_account"
|
accountID := "test_account"
|
||||||
adminUser := server.NewAdminUser("test_user")
|
adminUser := server.NewAdminUser("test_user")
|
||||||
|
|
||||||
@@ -94,6 +92,7 @@ func TestAccounts_AccountsHandler(t *testing.T) {
|
|||||||
expectedSettings: api.AccountSettings{
|
expectedSettings: api.AccountSettings{
|
||||||
PeerLoginExpiration: int(time.Hour.Seconds()),
|
PeerLoginExpiration: int(time.Hour.Seconds()),
|
||||||
PeerLoginExpirationEnabled: false,
|
PeerLoginExpirationEnabled: false,
|
||||||
|
GroupsPropagationEnabled: br(false),
|
||||||
JwtGroupsClaimName: sr(""),
|
JwtGroupsClaimName: sr(""),
|
||||||
JwtGroupsEnabled: br(false),
|
JwtGroupsEnabled: br(false),
|
||||||
},
|
},
|
||||||
@@ -110,6 +109,7 @@ func TestAccounts_AccountsHandler(t *testing.T) {
|
|||||||
expectedSettings: api.AccountSettings{
|
expectedSettings: api.AccountSettings{
|
||||||
PeerLoginExpiration: 15552000,
|
PeerLoginExpiration: 15552000,
|
||||||
PeerLoginExpirationEnabled: true,
|
PeerLoginExpirationEnabled: true,
|
||||||
|
GroupsPropagationEnabled: br(false),
|
||||||
JwtGroupsClaimName: sr(""),
|
JwtGroupsClaimName: sr(""),
|
||||||
JwtGroupsEnabled: br(false),
|
JwtGroupsEnabled: br(false),
|
||||||
},
|
},
|
||||||
@@ -126,12 +126,30 @@ func TestAccounts_AccountsHandler(t *testing.T) {
|
|||||||
expectedSettings: api.AccountSettings{
|
expectedSettings: api.AccountSettings{
|
||||||
PeerLoginExpiration: 15552000,
|
PeerLoginExpiration: 15552000,
|
||||||
PeerLoginExpirationEnabled: false,
|
PeerLoginExpirationEnabled: false,
|
||||||
|
GroupsPropagationEnabled: br(false),
|
||||||
JwtGroupsClaimName: sr("roles"),
|
JwtGroupsClaimName: sr("roles"),
|
||||||
JwtGroupsEnabled: br(true),
|
JwtGroupsEnabled: br(true),
|
||||||
},
|
},
|
||||||
expectedArray: false,
|
expectedArray: false,
|
||||||
expectedID: accountID,
|
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",
|
name: "Update account failure with high peer_login_expiration more than 180 days",
|
||||||
expectedBody: true,
|
expectedBody: true,
|
||||||
|
|||||||
@@ -54,6 +54,10 @@ components:
|
|||||||
description: Period of time after which peer login expires (seconds).
|
description: Period of time after which peer login expires (seconds).
|
||||||
type: integer
|
type: integer
|
||||||
example: 43200
|
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:
|
jwt_groups_enabled:
|
||||||
description: Allows extract groups from JWT claim and add it to account groups.
|
description: Allows extract groups from JWT claim and add it to account groups.
|
||||||
type: boolean
|
type: boolean
|
||||||
@@ -369,7 +373,9 @@ components:
|
|||||||
expires_in:
|
expires_in:
|
||||||
description: Expiration time in seconds
|
description: Expiration time in seconds
|
||||||
type: integer
|
type: integer
|
||||||
example: 43200
|
minimum: 86400
|
||||||
|
maximum: 31536000
|
||||||
|
example: 86400
|
||||||
revoked:
|
revoked:
|
||||||
description: Setup key revocation status
|
description: Setup key revocation status
|
||||||
type: boolean
|
type: boolean
|
||||||
|
|||||||
@@ -129,6 +129,9 @@ type AccountRequest struct {
|
|||||||
|
|
||||||
// AccountSettings defines model for AccountSettings.
|
// AccountSettings defines model for AccountSettings.
|
||||||
type AccountSettings struct {
|
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 Name of the claim from which we extract groups names to add it to account groups.
|
||||||
JwtGroupsClaimName *string `json:"jwt_groups_claim_name,omitempty"`
|
JwtGroupsClaimName *string `json:"jwt_groups_claim_name,omitempty"`
|
||||||
|
|
||||||
|
|||||||
@@ -60,6 +60,13 @@ func (h *SetupKeysHandler) CreateSetupKey(w http.ResponseWriter, r *http.Request
|
|||||||
|
|
||||||
expiresIn := time.Duration(req.ExpiresIn) * time.Second
|
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 {
|
if req.AutoGroups == nil {
|
||||||
req.AutoGroups = []string{}
|
req.AutoGroups = []string{}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ func TestSetupKeysHandlers(t *testing.T) {
|
|||||||
requestType: http.MethodPost,
|
requestType: http.MethodPost,
|
||||||
requestPath: "/api/setup-keys",
|
requestPath: "/api/setup-keys",
|
||||||
requestBody: bytes.NewBuffer(
|
requestBody: bytes.NewBuffer(
|
||||||
[]byte(fmt.Sprintf("{\"name\":\"%s\",\"type\":\"%s\"}", newSetupKey.Name, newSetupKey.Type))),
|
[]byte(fmt.Sprintf("{\"name\":\"%s\",\"type\":\"%s\",\"expires_in\":86400}", newSetupKey.Name, newSetupKey.Type))),
|
||||||
expectedStatus: http.StatusOK,
|
expectedStatus: http.StatusOK,
|
||||||
expectedBody: true,
|
expectedBody: true,
|
||||||
expectedSetupKey: toResponseBody(newSetupKey),
|
expectedSetupKey: toResponseBody(newSetupKey),
|
||||||
|
|||||||
@@ -260,7 +260,6 @@ func (am *DefaultAccountManager) inviteNewUser(accountID, userID string, invite
|
|||||||
am.storeEvent(userID, newUser.Id, accountID, activity.UserInvited, nil)
|
am.storeEvent(userID, newUser.Id, accountID, activity.UserInvited, nil)
|
||||||
|
|
||||||
return newUser.ToUserInfo(idpUser)
|
return newUser.ToUserInfo(idpUser)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUser looks up a user by provided authorization claims.
|
// GetUser looks up a user by provided authorization claims.
|
||||||
@@ -600,6 +599,13 @@ 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 {
|
if err = am.Store.SaveAccount(account); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -640,7 +646,6 @@ func (am *DefaultAccountManager) SaveUser(accountID, initiatorUserID string, upd
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if !isNil(am.idpManager) && !newUser.IsServiceUser {
|
if !isNil(am.idpManager) && !newUser.IsServiceUser {
|
||||||
|
|||||||
Reference in New Issue
Block a user