mirror of
https://github.com/netbirdio/netbird.git
synced 2026-04-24 19:26:39 +00:00
Compare commits
10 Commits
ebpf-debug
...
fix/iptabl
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a9452e7b32 | ||
|
|
950d2efed7 | ||
|
|
a9b9b3fa0a | ||
|
|
cdf57275b7 | ||
|
|
e5e69b1f75 | ||
|
|
8eca83f3cb | ||
|
|
973316d194 | ||
|
|
a0a6ced148 | ||
|
|
0fc6c477a9 | ||
|
|
401a462398 |
81
README.md
81
README.md
@@ -36,46 +36,61 @@
|
|||||||
|
|
||||||
<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 isolates every machine and device by applying granular access policies, while allowing you to manage them intuitively from a single place.
|
||||||
|
|
||||||
**Key features:**
|
**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:**
|
| Connectivity | Management | Automation | Platforms |
|
||||||
- \[ ] Mobile clients.
|
|-------------------------------------------------------------------|--------------------------------------------------------------------------|----------------------------------------------------------------------------|---------------------------------------|
|
||||||
|
| <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> | | |
|
||||||
|
|
||||||
|
|
||||||
### 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.
|
### Quickstart with NetBird Cloud
|
||||||
For stable versions, see [releases](https://github.com/netbirdio/netbird/releases).
|
|
||||||
|
|
||||||
### Start using NetBird
|
- Download and install NetBird at [https://app.netbird.io/install](https://app.netbird.io/install)
|
||||||
- Hosted version: [https://app.netbird.io/](https://app.netbird.io/).
|
- Follow the steps to sign-up with Google, Microsoft, GitHub or your email address.
|
||||||
- See our documentation for [Quickstart Guide](https://docs.netbird.io/how-to/getting-started).
|
- Check NetBird [admin UI](https://app.netbird.io/).
|
||||||
- If you are looking to self-host NetBird, check our [Self-Hosting Guide](https://docs.netbird.io/selfhosted/selfhosted-guide).
|
- Add more machines.
|
||||||
- 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 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.
|
||||||
@@ -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.
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
|||||||
@@ -395,6 +395,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 +463,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 {
|
||||||
|
|||||||
@@ -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 {
|
||||||
if n > 0 {
|
account.UserGroupsAddToPeers(claims.UserId, append(user.AutoGroups, groups...)...)
|
||||||
|
}
|
||||||
|
}
|
||||||
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