Compare commits

..

7 Commits

Author SHA1 Message Date
Maycon Santos
d9fa28d8a0 test postgres 2023-08-02 18:56:06 +02:00
Maycon Santos
144ac868e0 create empty files 2023-08-02 18:32:31 +02:00
Maycon Santos
b70339d3bd use check_jq 2023-08-02 12:32:43 +02:00
Maycon Santos
ee890971a3 update texts and expose errors to stderr 2023-08-02 12:19:35 +02:00
Maycon Santos
eeb1b619b7 Automate Zitadel IDP configuration (#1006)
Add automated zitadel configuration and netbird using a single script

run infra files test only when they change or when pushed to main

run releases only on changes to related files or when pushed to main

push install and automated getting started script to releases page
2023-08-01 23:31:14 +02:00
Maycon Santos
4b47f6b23c Merge branch 'main' into update-getting-started-flow 2023-07-30 16:39:49 +02:00
Maycon Santos
5883e019c9 update readme 2023-07-07 17:05:21 +02:00
45 changed files with 300 additions and 599 deletions

View File

@@ -135,7 +135,7 @@ jobs:
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: run script - name: run script
run: NETBIRD_DOMAIN=use-ip bash -x infrastructure_files/getting-started-with-zitadel.sh run: bash -x infrastructure_files/getting-started-with-zitadel.sh
- name: test Caddy file gen - name: test Caddy file gen
run: test -f Caddyfile run: test -f Caddyfile

View File

@@ -378,11 +378,6 @@ uploads:
username: dev@wiretrustee.com username: dev@wiretrustee.com
method: PUT method: PUT
checksum:
extra_files:
- glob: ./infrastructure_files/getting-started-with-zitadel.sh
- glob: ./release_files/install.sh
release: release:
extra_files: extra_files:
- glob: ./infrastructure_files/getting-started-with-zitadel.sh - glob: ./infrastructure_files/getting-started-with-zitadel.sh

View File

@@ -1,6 +1,6 @@
<p align="center"> <p align="center">
<strong>:hatching_chick: New Release! Self-hosting in under 5 min.</strong> <strong>:hatching_chick: New Release! Peer expiration.</strong>
<a href="https://github.com/netbirdio/netbird#quickstart-with-self-hosted-netbird"> <a href="https://github.com/netbirdio/netbird/releases">
Learn more Learn more
</a> </a>
</p> </p>
@@ -36,62 +36,48 @@
<br> <br>
**NetBird combines a configuration-free peer-to-peer private network and a centralized access control system in a single platform, making it easy to create secure private networks for your organization or home.** **NetBird is an open-source VPN management platform built on top of WireGuard® making it easy to create secure private networks for your organization or home.**
**Connect.** NetBird creates a WireGuard-based overlay network that automatically connects your machines over an encrypted tunnel, leaving behind the hassle of opening ports, complex firewall rules, VPN gateways, and so forth. It requires zero configuration effort leaving behind the hassle of opening ports, complex firewall rules, VPN gateways, and so forth.
**Secure.** NetBird enables secure remote access by applying granular access policies, while allowing you to manage them intuitively from a single place. Works universally on any infrastructure. NetBird uses [NAT traversal techniques](https://en.wikipedia.org/wiki/Interactive_Connectivity_Establishment) to automatically create an overlay peer-to-peer network connecting machines regardless of location (home, office, data center, container, cloud, or edge environments), unifying virtual private network management experience.
**Key features:**
- \[x] Automatic IP allocation and network management with a Web UI ([separate repo](https://github.com/netbirdio/dashboard))
- \[x] Automatic WireGuard peer (machine) discovery and configuration.
- \[x] Encrypted peer-to-peer connections without a central VPN gateway.
- \[x] Connection relay fallback in case a peer-to-peer connection is not possible.
- \[x] Desktop client applications for Linux, MacOS, and Windows (systray).
- \[x] Multiuser support - sharing network between multiple users.
- \[x] SSO and MFA support.
- \[x] Multicloud and hybrid-cloud support.
- \[x] Kernel WireGuard usage when possible.
- \[x] Access Controls - groups & rules.
- \[x] Remote SSH access without managing SSH keys.
- \[x] Network Routes.
- \[x] Private DNS.
- \[x] Network Activity Monitoring.
- \[x] Mobile clients (Android).
-
**Coming soon:**
- \[ ] Mobile clients (iOS).
### 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
### Key features **Note**: The `main` branch may be in an *unstable or even broken state* during development.
For stable versions, see [releases](https://github.com/netbirdio/netbird/releases).
| Connectivity | Management | Automation | Platforms | ### Start using NetBird
|-------------------------------------------------------------------|--------------------------------------------------------------------------|----------------------------------------------------------------------------|---------------------------------------| - Hosted version: [https://app.netbird.io/](https://app.netbird.io/).
| <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> | - See our documentation for [Quickstart Guide](https://docs.netbird.io/how-to/getting-started).
| <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> | - 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 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> | - Step-by-step [Installation Guide](https://docs.netbird.io/how-to/getting-started#installation) for different platforms.
| <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> | - Web UI [repository](https://github.com/netbirdio/dashboard).
| <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> | - 5 min [demo video](https://youtu.be/Tu9tPsUWaY0) on YouTube.
| <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).
@@ -103,18 +89,18 @@ export NETBIRD_DOMAIN=netbird.example.com; curl -fsSL https://github.com/netbird
[Coturn](https://github.com/coturn/coturn) is the one that has been successfully used for STUN and TURN in NetBird setups. [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://docs.netbird.io/docs-static/img/architecture/high-level-dia.png" width="700"/> <img src="https://netbird.io/docs/img/architecture/high-level-dia.png" width="700"/>
</p> </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.
@@ -122,7 +108,7 @@ In November 2022, NetBird joined the [StartUpSecure program](https://www.forschu
![CISPA_Logo_BLACK_EN_RZ_RGB (1)](https://user-images.githubusercontent.com/700848/203091324-c6d311a0-22b5-4b05-a288-91cbc6cdcc46.png) ![CISPA_Logo_BLACK_EN_RZ_RGB (1)](https://user-images.githubusercontent.com/700848/203091324-c6d311a0-22b5-4b05-a288-91cbc6cdcc46.png)
### Testimonials ### Testimonials
We use open-source technologies like [WireGuard®](https://www.wireguard.com/), [Pion ICE (WebRTC)](https://github.com/pion/ice), [Coturn](https://github.com/coturn/coturn), and [Rosenpass](https://rosenpass.eu). We very much appreciate the work these guys are doing and we'd greatly appreciate if you could support them in any way (e.g. giving a star or a contribution). We use open-source technologies like [WireGuard®](https://www.wireguard.com/), [Pion ICE (WebRTC)](https://github.com/pion/ice), and [Coturn](https://github.com/coturn/coturn). We very much appreciate the work these guys are doing and we'd greatly appreciate if you could support them in any way (e.g. giving a star or a contribution).
### Legal ### 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.

View File

@@ -1,5 +1,7 @@
FROM alpine:3 FROM gcr.io/distroless/base:debug
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

View File

@@ -55,6 +55,7 @@ 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
@@ -96,30 +97,7 @@ 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)
return internal.RunClientMobile(ctx, cfg, c.recorder, c.tunAdapter, c.iFaceDiscover, c.routeListener, dns.items, dnsReadyListener) c.onHostDnsFn = func([]string) {}
}
// 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)
} }

View File

@@ -25,8 +25,6 @@ var _ OAuthFlow = &PKCEAuthorizationFlow{}
const ( const (
queryState = "state" queryState = "state"
queryCode = "code" queryCode = "code"
queryError = "error"
queryErrorDesc = "error_description"
defaultPKCETimeoutSeconds = 300 defaultPKCETimeoutSeconds = 300
) )
@@ -143,13 +141,9 @@ func (p *PKCEAuthorizationFlow) startServer(tokenChan chan<- *oauth2.Token, errC
tokenValidatorFunc := func() (*oauth2.Token, error) { tokenValidatorFunc := func() (*oauth2.Token, error) {
query := req.URL.Query() query := req.URL.Query()
if authError := query.Get(queryError); authError != "" { state := query.Get(queryState)
authErrorDesc := query.Get(queryErrorDesc)
return nil, fmt.Errorf("%s.%s", authError, authErrorDesc)
}
// Prevent timing attacks on state // Prevent timing attacks on state
if state := query.Get(queryState); subtle.ConstantTimeCompare([]byte(p.state), []byte(state)) == 0 { if subtle.ConstantTimeCompare([]byte(p.state), []byte(state)) == 0 {
return nil, fmt.Errorf("invalid state") return nil, fmt.Errorf("invalid state")
} }
@@ -167,13 +161,12 @@ func (p *PKCEAuthorizationFlow) startServer(tokenChan chan<- *oauth2.Token, errC
token, err := tokenValidatorFunc() token, err := tokenValidatorFunc()
if err != nil { if err != nil {
renderPKCEFlowTmpl(w, err)
errChan <- fmt.Errorf("PKCE authorization flow failed: %v", err) errChan <- fmt.Errorf("PKCE authorization flow failed: %v", err)
return renderPKCEFlowTmpl(w, err)
} }
renderPKCEFlowTmpl(w, nil)
tokenChan <- token tokenChan <- token
renderPKCEFlowTmpl(w, nil)
}) })
if err := server.ListenAndServe(); err != nil { if err := server.ListenAndServe(); err != nil {

View File

@@ -179,6 +179,8 @@ func runClient(ctx context.Context, config *Config, statusRecorder *peer.Status,
log.Print("Netbird engine started, my IP is: ", peerConfig.Address) 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()
@@ -199,7 +201,6 @@ 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)

View File

@@ -15,8 +15,7 @@ const (
fileGeneratedResolvConfSearchBeginContent = "search " fileGeneratedResolvConfSearchBeginContent = "search "
fileGeneratedResolvConfContentFormat = fileGeneratedResolvConfContentHeader + fileGeneratedResolvConfContentFormat = fileGeneratedResolvConfContentHeader +
"\n# If needed you can restore the original file by copying back %s\n\nnameserver %s\n" + "\n# If needed you can restore the original file by copying back %s\n\nnameserver %s\n" +
fileGeneratedResolvConfSearchBeginContent + "%s\n\n" + fileGeneratedResolvConfSearchBeginContent + "%s\n"
"%s\n"
) )
const ( const (
@@ -92,12 +91,7 @@ func (f *fileConfigurator) applyDNSConfig(config hostDNSConfig) error {
searchDomains += " " + dConf.domain searchDomains += " " + dConf.domain
appendedDomains++ appendedDomains++
} }
content := fmt.Sprintf(fileGeneratedResolvConfContentFormat, fileDefaultResolvConfBackupLocation, config.serverIP, searchDomains)
originalContent, err := os.ReadFile(fileDefaultResolvConfBackupLocation)
if err != nil {
log.Errorf("Could not read existing resolv.conf")
}
content := fmt.Sprintf(fileGeneratedResolvConfContentFormat, fileDefaultResolvConfBackupLocation, config.serverIP, searchDomains, string(originalContent))
err = writeDNSConfig(content, defaultResolvConfPath, f.originalPerms) err = writeDNSConfig(content, defaultResolvConfPath, f.originalPerms)
if err != nil { if err != nil {
err = f.restore() err = f.restore()

View File

@@ -182,11 +182,12 @@ func (s *systemConfigurator) addDNSState(state, domains, dnsServer string, port
} }
func (s *systemConfigurator) addDNSSetupForAll(dnsServer string, port int) error { func (s *systemConfigurator) addDNSSetupForAll(dnsServer string, port int) error {
primaryServiceKey, existingNameserver := s.getPrimaryService() primaryServiceKey := s.getPrimaryService()
if primaryServiceKey == "" { if primaryServiceKey == "" {
return fmt.Errorf("couldn't find the primary service key") return fmt.Errorf("couldn't find the primary service key")
} }
err := s.addDNSSetup(getKeyWithInput(primaryServiceSetupKeyFormat, primaryServiceKey), dnsServer, port, existingNameserver)
err := s.addDNSSetup(getKeyWithInput(primaryServiceSetupKeyFormat, primaryServiceKey), dnsServer, port)
if err != nil { if err != nil {
return err return err
} }
@@ -195,32 +196,27 @@ func (s *systemConfigurator) addDNSSetupForAll(dnsServer string, port int) error
return nil return nil
} }
func (s *systemConfigurator) getPrimaryService() (string, string) { func (s *systemConfigurator) getPrimaryService() string {
line := buildCommandLine("show", globalIPv4State, "") line := buildCommandLine("show", globalIPv4State, "")
stdinCommands := wrapCommand(line) stdinCommands := wrapCommand(line)
b, err := runSystemConfigCommand(stdinCommands) b, err := runSystemConfigCommand(stdinCommands)
if err != nil { if err != nil {
log.Error("got error while sending the command: ", err) log.Error("got error while sending the command: ", err)
return "", "" return ""
} }
scanner := bufio.NewScanner(bytes.NewReader(b)) scanner := bufio.NewScanner(bytes.NewReader(b))
primaryService := ""
router := ""
for scanner.Scan() { for scanner.Scan() {
text := scanner.Text() text := scanner.Text()
if strings.Contains(text, "PrimaryService") { if strings.Contains(text, "PrimaryService") {
primaryService = strings.TrimSpace(strings.Split(text, ":")[1]) return strings.TrimSpace(strings.Split(text, ":")[1])
}
if strings.Contains(text, "Router") {
router = strings.TrimSpace(strings.Split(text, ":")[1])
} }
} }
return primaryService, router return ""
} }
func (s *systemConfigurator) addDNSSetup(setupKey, dnsServer string, port int, existingDNSServer string) error { func (s *systemConfigurator) addDNSSetup(setupKey, dnsServer string, port int) error {
lines := buildAddCommandLine(keySupplementalMatchDomainsNoSearch, digitSymbol+strconv.Itoa(0)) lines := buildAddCommandLine(keySupplementalMatchDomainsNoSearch, digitSymbol+strconv.Itoa(0))
lines += buildAddCommandLine(keyServerAddresses, arraySymbol+dnsServer+" "+existingDNSServer) lines += buildAddCommandLine(keyServerAddresses, arraySymbol+dnsServer)
lines += buildAddCommandLine(keyServerPort, digitSymbol+strconv.Itoa(port)) lines += buildAddCommandLine(keyServerPort, digitSymbol+strconv.Itoa(port))
addDomainCommand := buildCreateStateWithOperation(setupKey, lines) addDomainCommand := buildCreateStateWithOperation(setupKey, lines)
stdinCommands := wrapCommand(addDomainCommand) stdinCommands := wrapCommand(addDomainCommand)

View File

@@ -4,7 +4,6 @@ package dns
import ( import (
"fmt" "fmt"
"os"
"os/exec" "os/exec"
"strings" "strings"
@@ -60,11 +59,7 @@ func (r *resolvconf) applyDNSConfig(config hostDNSConfig) error {
appendedDomains++ appendedDomains++
} }
originalContent, err := os.ReadFile(fileDefaultResolvConfBackupLocation) content := fmt.Sprintf(fileGeneratedResolvConfContentFormat, fileDefaultResolvConfBackupLocation, config.serverIP, searchDomains)
if err != nil {
log.Errorf("Could not read existing resolv.conf")
}
content := fmt.Sprintf(fileGeneratedResolvConfContentFormat, fileDefaultResolvConfBackupLocation, config.serverIP, searchDomains, string(originalContent))
err = r.applyConfig(content) err = r.applyConfig(content)
if err != nil { if err != nil {

View File

@@ -17,7 +17,6 @@ type notifier struct {
listener Listener listener Listener
currentClientState bool currentClientState bool
lastNotification int lastNotification int
lastNumberOfPeers int
} }
func newNotifier() *notifier { func newNotifier() *notifier {
@@ -30,7 +29,6 @@ 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
@@ -61,7 +59,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 = stateConnecting n.lastNotification = stateConnected
n.notify(n.lastNotification) n.notify(n.lastNotification)
} }
@@ -114,7 +112,7 @@ func (n *notifier) calculateState(managementConn, signalConn bool) int {
return stateConnected return stateConnected
} }
if !managementConn && !signalConn && !n.currentClientState { if !managementConn && !signalConn {
return stateDisconnected return stateDisconnected
} }
@@ -126,7 +124,6 @@ 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 {

View File

@@ -353,13 +353,9 @@ func (d *Status) onConnectionChanged() {
} }
func (d *Status) notifyPeerListChanged() { func (d *Status) notifyPeerListChanged() {
d.notifier.peerListChanged(d.numOfPeers()) d.notifier.peerListChanged(len(d.peers) + len(d.offlinePeers))
} }
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)
}

View File

@@ -27,19 +27,14 @@ func genKey(format string, input string) string {
} }
// NewFirewall if supported, returns an iptables manager, otherwise returns a nftables manager // NewFirewall if supported, returns an iptables manager, otherwise returns a nftables manager
func NewFirewall(parentCTX context.Context) (firewallManager, error) { func NewFirewall(parentCTX context.Context) firewallManager {
manager, err := newNFTablesManager(parentCTX) 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, nil return manager
} }
fMgr, err := newIptablesManager(parentCTX) log.Debugf("fallback to iptables firewall manager: %s", err)
if err != nil { return newIptablesManager(parentCTX)
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 {

View File

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

View File

@@ -49,29 +49,26 @@ type iptablesManager struct {
mux sync.Mutex mux sync.Mutex
} }
func newIptablesManager(parentCtx context.Context) (*iptablesManager, error) { func newIptablesManager(parentCtx context.Context) *iptablesManager {
ipv4Client, err := iptables.NewWithProtocol(iptables.ProtocolIPv4) ctx, cancel := context.WithCancel(parentCtx)
if err != nil { ipv4Client, _ := iptables.NewWithProtocol(iptables.ProtocolIPv4)
return nil, err 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, _ := iptables.NewWithProtocol(iptables.ProtocolIPv6)
if err != nil { if !isIptablesClientAvailable(ipv6Client) {
log.Debugf("failed to initialize iptables for ipv6: %s", err)
} else if !isIptablesClientAvailable(ipv6Client) {
log.Infof("iptables is missing for ipv6") log.Infof("iptables is missing for ipv6")
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
@@ -394,10 +391,6 @@ 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]
@@ -462,10 +455,6 @@ 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 {

View File

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

View File

@@ -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,17 +36,13 @@ 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 {
serverRouter, err := newServerRouter(ctx, wgInterface)
if err != nil {
log.Errorf("server router is not supported: %s", err)
}
mCTX, cancel := context.WithCancel(ctx) 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: serverRouter, serverRouter: newServerRouter(ctx, wgInterface),
statusRecorder: statusRecorder, statusRecorder: statusRecorder,
wgInterface: wgInterface, wgInterface: wgInterface,
pubKey: pubKey, pubKey: pubKey,
@@ -63,9 +59,7 @@ func NewManager(ctx context.Context, pubKey string, wgInterface *iface.WGIface,
// Stop stops the manager watchers and clean firewall rules // 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
} }
@@ -83,12 +77,9 @@ 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)
err := m.serverRouter.updateRoutes(newServerRoutesMap)
if m.serverRouter != nil { if err != nil {
err := m.serverRouter.updateRoutes(newServerRoutesMap) return err
if err != nil {
return err
}
} }
return nil return nil

View File

@@ -30,6 +30,7 @@ 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
}{ }{
@@ -86,6 +87,7 @@ func TestManagerUpdateRoutes(t *testing.T) {
}, },
}, },
inputSerial: 1, inputSerial: 1,
shouldCheckServerRoutes: runtime.GOOS == "linux",
serverRoutesExpected: 2, serverRoutesExpected: 2,
clientNetworkWatchersExpected: 0, clientNetworkWatchersExpected: 0,
}, },
@@ -114,6 +116,7 @@ func TestManagerUpdateRoutes(t *testing.T) {
}, },
}, },
inputSerial: 1, inputSerial: 1,
shouldCheckServerRoutes: runtime.GOOS == "linux",
serverRoutesExpected: 1, serverRoutesExpected: 1,
clientNetworkWatchersExpected: 1, clientNetworkWatchersExpected: 1,
}, },
@@ -171,6 +174,25 @@ 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{
@@ -313,6 +335,7 @@ 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,
}, },
@@ -361,6 +384,7 @@ func TestManagerUpdateRoutes(t *testing.T) {
}, },
}, },
inputSerial: 1, inputSerial: 1,
shouldCheckServerRoutes: runtime.GOOS == "linux",
serverRoutesExpected: 2, serverRoutesExpected: 2,
clientNetworkWatchersExpected: 1, clientNetworkWatchersExpected: 1,
}, },
@@ -395,9 +419,8 @@ 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 runtime.GOOS == "linux" { if testCase.shouldCheckServerRoutes {
sr := routeManager.serverRouter.(*defaultServerRouter) require.Len(t, routeManager.serverRouter.routes, testCase.serverRoutesExpected, "server networks size should match")
require.Len(t, sr.routes, testCase.serverRoutesExpected, "server networks size should match")
} }
}) })
} }

View File

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

View File

@@ -2,11 +2,20 @@ package routemanager
import ( import (
"context" "context"
"fmt"
"github.com/netbirdio/netbird/iface" "github.com/netbirdio/netbird/iface"
"github.com/netbirdio/netbird/route"
) )
func newServerRouter(context.Context, *iface.WGIface) (serverRouter, error) { type serverRouter struct {
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() {}

View File

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

View File

@@ -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.Tracef("ignoring interface %s - it is not allowed", iFace) log.Debugf("ignoring interface %s - it is not allowed", iFace)
return false return false
} }
} }

View File

@@ -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 {
NbWgProxy *ebpf.ProgramSpec `ebpf:"nb_wg_proxy"` XdpProgFunc *ebpf.ProgramSpec `ebpf:"xdp_prog_func"`
} }
// 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 {
NbWgProxySettingsMap *ebpf.MapSpec `ebpf:"nb_wg_proxy_settings_map"` XdpPortMap *ebpf.MapSpec `ebpf:"xdp_port_map"`
} }
// bpfObjects contains all objects after they have been loaded into the kernel. // 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 {
NbWgProxySettingsMap *ebpf.Map `ebpf:"nb_wg_proxy_settings_map"` XdpPortMap *ebpf.Map `ebpf:"xdp_port_map"`
} }
func (m *bpfMaps) Close() error { func (m *bpfMaps) Close() error {
return _BpfClose( return _BpfClose(
m.NbWgProxySettingsMap, m.XdpPortMap,
) )
} }
@@ -96,12 +96,12 @@ func (m *bpfMaps) Close() error {
// //
// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign. // It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign.
type bpfPrograms struct { type bpfPrograms struct {
NbWgProxy *ebpf.Program `ebpf:"nb_wg_proxy"` XdpProgFunc *ebpf.Program `ebpf:"xdp_prog_func"`
} }
func (p *bpfPrograms) Close() error { func (p *bpfPrograms) Close() error {
return _BpfClose( return _BpfClose(
p.NbWgProxy, p.XdpProgFunc,
) )
} }

View File

@@ -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 {
NbWgProxy *ebpf.ProgramSpec `ebpf:"nb_wg_proxy"` XdpProgFunc *ebpf.ProgramSpec `ebpf:"xdp_prog_func"`
} }
// 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 {
NbWgProxySettingsMap *ebpf.MapSpec `ebpf:"nb_wg_proxy_settings_map"` XdpPortMap *ebpf.MapSpec `ebpf:"xdp_port_map"`
} }
// bpfObjects contains all objects after they have been loaded into the kernel. // 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 {
NbWgProxySettingsMap *ebpf.Map `ebpf:"nb_wg_proxy_settings_map"` XdpPortMap *ebpf.Map `ebpf:"xdp_port_map"`
} }
func (m *bpfMaps) Close() error { func (m *bpfMaps) Close() error {
return _BpfClose( return _BpfClose(
m.NbWgProxySettingsMap, m.XdpPortMap,
) )
} }
@@ -96,12 +96,12 @@ func (m *bpfMaps) Close() error {
// //
// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign. // It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign.
type bpfPrograms struct { type bpfPrograms struct {
NbWgProxy *ebpf.Program `ebpf:"nb_wg_proxy"` XdpProgFunc *ebpf.Program `ebpf:"xdp_prog_func"`
} }
func (p *bpfPrograms) Close() error { func (p *bpfPrograms) Close() error {
return _BpfClose( return _BpfClose(
p.NbWgProxy, p.XdpProgFunc,
) )
} }

View File

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

View File

@@ -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") nb_wg_proxy_settings_map = { struct bpf_map_def SEC("maps") xdp_port_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(&nb_wg_proxy_settings_map, &map_key_proxy_port); value = bpf_map_lookup_elem(&xdp_port_map, &map_key_proxy_port);
if(!value) { if(!value) {
return false; return false;
} }
proxy_port = *value; proxy_port = *value;
value = bpf_map_lookup_elem(&nb_wg_proxy_settings_map, &map_key_wg_port); value = bpf_map_lookup_elem(&xdp_port_map, &map_key_wg_port);
if(!value) { if(!value) {
return false; return false;
} }
@@ -44,7 +44,7 @@ bool read_port_settings() {
} }
SEC("xdp") SEC("xdp")
int nb_wg_proxy(struct xdp_md *ctx) { int xdp_prog_func(struct xdp_md *ctx) {
if(proxy_port == 0 || wg_port == 0) { if(proxy_port == 0 || wg_port == 0) {
if(!read_port_settings()){ if(!read_port_settings()){
return XDP_PASS; return XDP_PASS;

View File

@@ -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.Free() return w.ebpfProxy.CloseConn()
} }
return nil return nil
} }

View File

@@ -12,7 +12,7 @@ func NewFactory(wgPort int) *Factory {
ebpfProxy := NewWGEBPFProxy(wgPort) ebpfProxy := NewWGEBPFProxy(wgPort)
err := ebpfProxy.Listen() err := ebpfProxy.Listen()
if err != nil { if err != nil {
log.Warnf("failed to initialize ebpf proxy, fallback to user space proxy: %s", err) log.Errorf("failed to initialize ebpf proxy: %s", err)
return f return f
} }

View File

@@ -69,7 +69,7 @@ func (p *WGEBPFProxy) Listen() error {
p.conn, err = net.ListenUDP("udp", &addr) p.conn, err = net.ListenUDP("udp", &addr)
if err != nil { if err != nil {
cErr := p.Free() cErr := p.Free()
if cErr != nil { if err != nil {
log.Errorf("failed to close the wgproxy: %s", cErr) log.Errorf("failed to close the wgproxy: %s", cErr)
} }
return err return err
@@ -104,7 +104,6 @@ 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()
@@ -154,9 +153,7 @@ func (p *WGEBPFProxy) proxyToRemote() {
return return
} }
p.turnConnMutex.Lock()
conn, ok := p.turnConnStore[uint16(addr.Port)] conn, ok := p.turnConnStore[uint16(addr.Port)]
p.turnConnMutex.Unlock()
if !ok { if !ok {
log.Errorf("turn conn not found by port: %d", addr.Port) log.Errorf("turn conn not found by port: %d", addr.Port)
continue continue

View File

@@ -20,7 +20,6 @@ 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
View File

@@ -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.7.0 github.com/coreos/go-iptables v0.6.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
View File

@@ -131,8 +131,8 @@ github.com/containerd/typeurl v1.0.2/go.mod h1:9trJWW2sRlGub4wZJRTW83VtbOLS6hwcD
github.com/coocood/freecache v1.2.1 h1:/v1CqMq45NFH9mp/Pt142reundeBM0dVUD3osQBeu/U= github.com/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.7.0 h1:XWM3V+MPRr5/q51NuWSgU0fqMad64Zyxs8ZUoMsamr8= github.com/coreos/go-iptables v0.6.0 h1:is9qnZMPYjLd8LYqmm/qlE+wwEgJIkTYdhV3rfZo4jk=
github.com/coreos/go-iptables v0.7.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q= github.com/coreos/go-iptables v0.6.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-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=

View File

@@ -47,10 +47,10 @@ check_jq() {
fi fi
} }
wait_crdb() { wait_pgdb() {
set +e set +e
while true; do while true; do
if $DOCKER_COMPOSE_COMMAND exec -T crdb curl -sf -o /dev/null 'http://localhost:8080/health?ready=1'; then if $DOCKER_COMPOSE_COMMAND exec -T pgdb pg_isready -U postgres; then
break break
fi fi
echo -n " ." echo -n " ."
@@ -60,15 +60,15 @@ wait_crdb() {
set -e set -e
} }
init_crdb() { init_pgdb() {
echo -e "\nInitializing Zitadel's CockroachDB\n\n" echo -e "\nInitializing Zitadel's CockroachDB\n\n"
$DOCKER_COMPOSE_COMMAND up -d crdb $DOCKER_COMPOSE_COMMAND up -d pgdb
echo "" echo ""
# shellcheck disable=SC2028 # shellcheck disable=SC2028
echo -n "Waiting cockroachDB to become ready " echo -n "Waiting cockroachDB to become ready "
wait_crdb wait_pgdb
$DOCKER_COMPOSE_COMMAND exec -T crdb /bin/bash -c "cp /cockroach/certs/* /zitadel-certs/ && cockroach cert create-client --overwrite --certs-dir /zitadel-certs/ --ca-key /zitadel-certs/ca.key zitadel_user && chown -R 1000:1000 /zitadel-certs/" #$DOCKER_COMPOSE_COMMAND exec -T pgdb /bin/bash -c "cp /cockroach/certs/* /zitadel-certs/ && cockroach cert create-client --overwrite --certs-dir /zitadel-certs/ --ca-key /zitadel-certs/ca.key zitadel_user && chown -R 1000:1000 /zitadel-certs/"
handle_request_command_status $? "init_crdb failed" "" handle_request_command_status $? "init_pgdb failed" ""
} }
get_main_ip_address() { get_main_ip_address() {
@@ -135,8 +135,7 @@ create_new_application() {
APPLICATION_NAME=$3 APPLICATION_NAME=$3
BASE_REDIRECT_URL1=$4 BASE_REDIRECT_URL1=$4
BASE_REDIRECT_URL2=$5 BASE_REDIRECT_URL2=$5
LOGOUT_URL=$6 ZITADEL_DEV_MODE=$6
ZITADEL_DEV_MODE=$7
RESPONSE=$( RESPONSE=$(
curl -sS -X POST "$INSTANCE_URL/management/v1/projects/$PROJECT_ID/apps/oidc" \ curl -sS -X POST "$INSTANCE_URL/management/v1/projects/$PROJECT_ID/apps/oidc" \
@@ -149,7 +148,7 @@ create_new_application() {
"'"$BASE_REDIRECT_URL2"'" "'"$BASE_REDIRECT_URL2"'"
], ],
"postLogoutRedirectUris": [ "postLogoutRedirectUris": [
"'"$LOGOUT_URL"'" "'"$BASE_REDIRECT_URL1"'"
], ],
"RESPONSETypes": [ "RESPONSETypes": [
"OIDC_RESPONSE_TYPE_CODE" "OIDC_RESPONSE_TYPE_CODE"
@@ -340,10 +339,10 @@ init_zitadel() {
# create zitadel spa applications # create zitadel spa applications
echo "Creating new Zitadel SPA Dashboard application" echo "Creating new Zitadel SPA Dashboard application"
DASHBOARD_APPLICATION_CLIENT_ID=$(create_new_application "$INSTANCE_URL" "$PAT" "Dashboard" "$BASE_REDIRECT_URL/nb-auth" "$BASE_REDIRECT_URL/nb-silent-auth" "$BASE_REDIRECT_URL/" "$ZITADEL_DEV_MODE") DASHBOARD_APPLICATION_CLIENT_ID=$(create_new_application "$INSTANCE_URL" "$PAT" "Dashboard" "$BASE_REDIRECT_URL/nb-auth" "$BASE_REDIRECT_URL/nb-silent-auth" "$ZITADEL_DEV_MODE")
echo "Creating new Zitadel SPA Cli application" echo "Creating new Zitadel SPA Cli application"
CLI_APPLICATION_CLIENT_ID=$(create_new_application "$INSTANCE_URL" "$PAT" "Cli" "http://localhost:53000/" "http://localhost:54000/" "http://localhost:53000/" "true") CLI_APPLICATION_CLIENT_ID=$(create_new_application "$INSTANCE_URL" "$PAT" "Cli" "http://localhost:53000/" "http://localhost:54000/" "true")
MACHINE_USER_ID=$(create_service_user "$INSTANCE_URL" "$PAT") MACHINE_USER_ID=$(create_service_user "$INSTANCE_URL" "$PAT")
@@ -378,35 +377,12 @@ init_zitadel() {
export ZITADEL_ADMIN_PASSWORD export ZITADEL_ADMIN_PASSWORD
} }
check_nb_domain() {
DOMAIN=$1
if [ "$DOMAIN-x" == "-x" ]; then
echo "The NETBIRD_DOMAIN variable cannot be empty." > /dev/stderr
return 1
fi
if [ "$DOMAIN" == "netbird.example.com" ]; then
echo "The NETBIRD_DOMAIN cannot be netbird.example.com" > /dev/stderr
retrun 1
fi
return 0
}
read_nb_domain() {
READ_NETBIRD_DOMAIN=""
echo -n "Enter the domain you want to use for NetBird (e.g. netbird.my-domain.com): " > /dev/stderr
read -r READ_NETBIRD_DOMAIN < /dev/tty
if ! check_nb_domain "$READ_NETBIRD_DOMAIN"; then
read_nb_domain
fi
echo "$READ_NETBIRD_DOMAIN"
}
initEnvironment() { initEnvironment() {
CADDY_SECURE_DOMAIN="" CADDY_SECURE_DOMAIN=""
ZITADEL_EXTERNALSECURE="false" ZITADEL_EXTERNALSECURE="false"
ZITADEL_TLS_MODE="disabled" ZITADEL_TLS_MODE="disabled"
ZITADEL_MASTERKEY="$(openssl rand -base64 32 | head -c 32)" ZITADEL_MASTERKEY="$(openssl rand -base64 32 | head -c 32)"
USING_DOMAIN="true"
NETBIRD_PORT=80 NETBIRD_PORT=80
NETBIRD_HTTP_PROTOCOL="http" NETBIRD_HTTP_PROTOCOL="http"
TURN_USER="self" TURN_USER="self"
@@ -414,13 +390,18 @@ initEnvironment() {
TURN_MIN_PORT=49152 TURN_MIN_PORT=49152
TURN_MAX_PORT=65535 TURN_MAX_PORT=65535
if ! check_nb_domain "$NETBIRD_DOMAIN"; then NETBIRD_DOMAIN=$NETBIRD_DOMAIN
NETBIRD_DOMAIN=$(read_nb_domain) if [ "$NETBIRD_DOMAIN-x" == "-x" ] ; then
echo "NETBIRD_DOMAIN is not set, using the main IP address"
NETBIRD_DOMAIN=$(get_main_ip_address)
USING_DOMAIN="false"
fi fi
if [ "$NETBIRD_DOMAIN" == "use-ip" ]; then if [ "$NETBIRD_DOMAIN" == "localhost" ]; then
NETBIRD_DOMAIN=$(get_main_ip_address) USING_DOMAIN="false"
else fi
if [ $USING_DOMAIN == "true" ]; then
ZITADEL_EXTERNALSECURE="true" ZITADEL_EXTERNALSECURE="true"
ZITADEL_TLS_MODE="external" ZITADEL_TLS_MODE="external"
NETBIRD_PORT=443 NETBIRD_PORT=443
@@ -458,7 +439,7 @@ initEnvironment() {
mkdir -p machinekey mkdir -p machinekey
chmod 777 machinekey chmod 777 machinekey
init_crdb init_pgdb
echo -e "\nStarting Zidatel IDP for user management\n\n" echo -e "\nStarting Zidatel IDP for user management\n\n"
$DOCKER_COMPOSE_COMMAND up -d caddy zitadel $DOCKER_COMPOSE_COMMAND up -d caddy zitadel
@@ -613,16 +594,25 @@ renderZitadelEnv() {
cat <<EOF cat <<EOF
ZITADEL_LOG_LEVEL=debug ZITADEL_LOG_LEVEL=debug
ZITADEL_MASTERKEY=$ZITADEL_MASTERKEY ZITADEL_MASTERKEY=$ZITADEL_MASTERKEY
ZITADEL_DATABASE_COCKROACH_HOST=crdb #ZITADEL_DATABASE_COCKROACH_HOST=pgdb
ZITADEL_DATABASE_COCKROACH_USER_USERNAME=zitadel_user #ZITADEL_DATABASE_COCKROACH_USER_USERNAME=zitadel_user
ZITADEL_DATABASE_COCKROACH_USER_SSL_MODE=verify-full #ZITADEL_DATABASE_COCKROACH_USER_SSL_MODE=verify-full
ZITADEL_DATABASE_COCKROACH_USER_SSL_ROOTCERT="/crdb-certs/ca.crt" #ZITADEL_DATABASE_COCKROACH_USER_SSL_ROOTCERT="/pgdb-certs/ca.crt"
ZITADEL_DATABASE_COCKROACH_USER_SSL_CERT="/crdb-certs/client.zitadel_user.crt" #ZITADEL_DATABASE_COCKROACH_USER_SSL_CERT="/pgdb-certs/client.zitadel_user.crt"
ZITADEL_DATABASE_COCKROACH_USER_SSL_KEY="/crdb-certs/client.zitadel_user.key" #ZITADEL_DATABASE_COCKROACH_USER_SSL_KEY="/pgdb-certs/client.zitadel_user.key"
ZITADEL_DATABASE_COCKROACH_ADMIN_SSL_MODE=verify-full #ZITADEL_DATABASE_COCKROACH_ADMIN_SSL_MODE=verify-full
ZITADEL_DATABASE_COCKROACH_ADMIN_SSL_ROOTCERT="/crdb-certs/ca.crt" #ZITADEL_DATABASE_COCKROACH_ADMIN_SSL_ROOTCERT="/pgdb-certs/ca.crt"
ZITADEL_DATABASE_COCKROACH_ADMIN_SSL_CERT="/crdb-certs/client.root.crt" #ZITADEL_DATABASE_COCKROACH_ADMIN_SSL_CERT="/pgdb-certs/client.root.crt"
ZITADEL_DATABASE_COCKROACH_ADMIN_SSL_KEY="/crdb-certs/client.root.key" #ZITADEL_DATABASE_COCKROACH_ADMIN_SSL_KEY="/pgdb-certs/client.root.key"
ZITADEL_DATABASE_POSTGRES_HOST=pgdb
ZITADEL_DATABASE_POSTGRES_PORT=5432
ZITADEL_DATABASE_POSTGRES_DATABASE=zitadeldb
ZITADEL_DATABASE_POSTGRES_ADMIN_USERNAME=zitadeladmin
ZITADEL_DATABASE_POSTGRES_ADMIN_PASSWORD=zitadeladmin
ZITADEL_DATABASE_POSTGRES_ADMIN_SSL_MODE=disable
ZITADEL_DATABASE_POSTGRES_USER_USERNAME=zitadeluser
ZITADEL_DATABASE_POSTGRES_USER_PASSWORD=zitadeluser
ZITADEL_DATABASE_POSTGRES_USER_SSL_MODE=disable
ZITADEL_EXTERNALSECURE=$ZITADEL_EXTERNALSECURE ZITADEL_EXTERNALSECURE=$ZITADEL_EXTERNALSECURE
ZITADEL_TLS_ENABLED="false" ZITADEL_TLS_ENABLED="false"
ZITADEL_EXTERNALPORT=$NETBIRD_PORT ZITADEL_EXTERNALPORT=$NETBIRD_PORT
@@ -674,11 +664,10 @@ services:
command: [ command: [
"--port", "80", "--port", "80",
"--log-file", "console", "--log-file", "console",
"--log-level", "info", "--log-level", "debug",
"--disable-anonymous-metrics=false", "--disable-anonymous-metrics=false",
"--single-account-mode-domain=netbird.selfhosted", "--single-account-mode-domain=netbird.selfhosted",
"--dns-domain=netbird.selfhosted", "--dns-domain=netbird.selfhosted",
"--idp-sign-key-refresh-enabled",
] ]
# Coturn, AKA relay server # Coturn, AKA relay server
coturn: coturn:
@@ -699,23 +688,33 @@ services:
env_file: env_file:
- ./zitadel.env - ./zitadel.env
depends_on: depends_on:
crdb: pgdb:
condition: 'service_healthy' condition: 'service_healthy'
volumes: volumes:
- ./machinekey:/machinekey - ./machinekey:/machinekey
- netbird_zitadel_certs:/crdb-certs:ro - netbird_zitadel_certs:/pgdb-certs:ro
healthcheck:
test: [ "CMD", "curl", "-f", "http://localhost:8080/debug/healthz" ]
interval: '10s'
timeout: '30s'
retries: 5
start_period: '20s'
# CockroachDB for zitadel # CockroachDB for zitadel
crdb: pgdb:
restart: 'always' restart: 'always'
networks: [netbird] networks: [netbird]
image: 'cockroachdb/cockroach:v22.2.2' image: 'postgres:15'
command: 'start-single-node --advertise-addr crdb' environment:
- POSTGRES_USER=zitadeladmin
- POSTGRES_PASSWORD=zitadeladmin
- POSTGRES_DB=zitadeldb
#command: 'start-single-node --advertise-addr pgdb'
volumes: volumes:
- netbird_crdb_data:/cockroach/cockroach-data - netbird_pgdb_data:/cockroach/cockroach-data
- netbird_crdb_certs:/cockroach/certs - netbird_pgdb_certs:/cockroach/certs
- netbird_zitadel_certs:/zitadel-certs - netbird_zitadel_certs:/zitadel-certs
healthcheck: healthcheck:
test: [ "CMD", "curl", "-f", "http://localhost:8080/health?ready=1" ] test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: '10s' interval: '10s'
timeout: '30s' timeout: '30s'
retries: 5 retries: 5
@@ -724,8 +723,8 @@ services:
volumes: volumes:
netbird_management: netbird_management:
netbird_caddy_data: netbird_caddy_data:
netbird_crdb_data: netbird_pgdb_data:
netbird_crdb_certs: netbird_pgdb_certs:
netbird_zitadel_certs: netbird_zitadel_certs:
networks: networks:

View File

@@ -371,24 +371,24 @@ func handlerFunc(gRPCHandler *grpc.Server, httpHandler http.Handler) http.Handle
} }
func loadMgmtConfig(mgmtConfigPath string) (*server.Config, error) { func loadMgmtConfig(mgmtConfigPath string) (*server.Config, error) {
loadedConfig := &server.Config{} config := &server.Config{}
_, err := util.ReadJson(mgmtConfigPath, loadedConfig) _, err := util.ReadJson(mgmtConfigPath, config)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if mgmtLetsencryptDomain != "" { if mgmtLetsencryptDomain != "" {
loadedConfig.HttpConfig.LetsEncryptDomain = mgmtLetsencryptDomain config.HttpConfig.LetsEncryptDomain = mgmtLetsencryptDomain
} }
if mgmtDataDir != "" { if mgmtDataDir != "" {
loadedConfig.Datadir = mgmtDataDir config.Datadir = mgmtDataDir
} }
if certKey != "" && certFile != "" { if certKey != "" && certFile != "" {
loadedConfig.HttpConfig.CertFile = certFile config.HttpConfig.CertFile = certFile
loadedConfig.HttpConfig.CertKey = certKey config.HttpConfig.CertKey = certKey
} }
oidcEndpoint := loadedConfig.HttpConfig.OIDCConfigEndpoint oidcEndpoint := config.HttpConfig.OIDCConfigEndpoint
if oidcEndpoint != "" { if oidcEndpoint != "" {
// if OIDCConfigEndpoint is specified, we can load DeviceAuthEndpoint and TokenEndpoint automatically // if OIDCConfigEndpoint is specified, we can load DeviceAuthEndpoint and TokenEndpoint automatically
log.Infof("loading OIDC configuration from the provided IDP configuration endpoint %s", oidcEndpoint) log.Infof("loading OIDC configuration from the provided IDP configuration endpoint %s", oidcEndpoint)
@@ -399,45 +399,45 @@ func loadMgmtConfig(mgmtConfigPath string) (*server.Config, error) {
log.Infof("loaded OIDC configuration from the provided IDP configuration endpoint: %s", oidcEndpoint) log.Infof("loaded OIDC configuration from the provided IDP configuration endpoint: %s", oidcEndpoint)
log.Infof("overriding HttpConfig.AuthIssuer with a new value %s, previously configured value: %s", log.Infof("overriding HttpConfig.AuthIssuer with a new value %s, previously configured value: %s",
oidcConfig.Issuer, loadedConfig.HttpConfig.AuthIssuer) oidcConfig.Issuer, config.HttpConfig.AuthIssuer)
loadedConfig.HttpConfig.AuthIssuer = oidcConfig.Issuer config.HttpConfig.AuthIssuer = oidcConfig.Issuer
log.Infof("overriding HttpConfig.AuthKeysLocation (JWT certs) with a new value %s, previously configured value: %s", log.Infof("overriding HttpConfig.AuthKeysLocation (JWT certs) with a new value %s, previously configured value: %s",
oidcConfig.JwksURI, loadedConfig.HttpConfig.AuthKeysLocation) oidcConfig.JwksURI, config.HttpConfig.AuthKeysLocation)
loadedConfig.HttpConfig.AuthKeysLocation = oidcConfig.JwksURI config.HttpConfig.AuthKeysLocation = oidcConfig.JwksURI
if !(loadedConfig.DeviceAuthorizationFlow == nil || strings.ToLower(loadedConfig.DeviceAuthorizationFlow.Provider) == string(server.NONE)) { if !(config.DeviceAuthorizationFlow == nil || strings.ToLower(config.DeviceAuthorizationFlow.Provider) == string(server.NONE)) {
log.Infof("overriding DeviceAuthorizationFlow.TokenEndpoint with a new value: %s, previously configured value: %s", log.Infof("overriding DeviceAuthorizationFlow.TokenEndpoint with a new value: %s, previously configured value: %s",
oidcConfig.TokenEndpoint, loadedConfig.DeviceAuthorizationFlow.ProviderConfig.TokenEndpoint) oidcConfig.TokenEndpoint, config.DeviceAuthorizationFlow.ProviderConfig.TokenEndpoint)
loadedConfig.DeviceAuthorizationFlow.ProviderConfig.TokenEndpoint = oidcConfig.TokenEndpoint config.DeviceAuthorizationFlow.ProviderConfig.TokenEndpoint = oidcConfig.TokenEndpoint
log.Infof("overriding DeviceAuthorizationFlow.DeviceAuthEndpoint with a new value: %s, previously configured value: %s", log.Infof("overriding DeviceAuthorizationFlow.DeviceAuthEndpoint with a new value: %s, previously configured value: %s",
oidcConfig.DeviceAuthEndpoint, loadedConfig.DeviceAuthorizationFlow.ProviderConfig.DeviceAuthEndpoint) oidcConfig.DeviceAuthEndpoint, config.DeviceAuthorizationFlow.ProviderConfig.DeviceAuthEndpoint)
loadedConfig.DeviceAuthorizationFlow.ProviderConfig.DeviceAuthEndpoint = oidcConfig.DeviceAuthEndpoint config.DeviceAuthorizationFlow.ProviderConfig.DeviceAuthEndpoint = oidcConfig.DeviceAuthEndpoint
u, err := url.Parse(oidcEndpoint) u, err := url.Parse(oidcEndpoint)
if err != nil { if err != nil {
return nil, err return nil, err
} }
log.Infof("overriding DeviceAuthorizationFlow.ProviderConfig.Domain with a new value: %s, previously configured value: %s", log.Infof("overriding DeviceAuthorizationFlow.ProviderConfig.Domain with a new value: %s, previously configured value: %s",
u.Host, loadedConfig.DeviceAuthorizationFlow.ProviderConfig.Domain) u.Host, config.DeviceAuthorizationFlow.ProviderConfig.Domain)
loadedConfig.DeviceAuthorizationFlow.ProviderConfig.Domain = u.Host config.DeviceAuthorizationFlow.ProviderConfig.Domain = u.Host
if loadedConfig.DeviceAuthorizationFlow.ProviderConfig.Scope == "" { if config.DeviceAuthorizationFlow.ProviderConfig.Scope == "" {
loadedConfig.DeviceAuthorizationFlow.ProviderConfig.Scope = server.DefaultDeviceAuthFlowScope config.DeviceAuthorizationFlow.ProviderConfig.Scope = server.DefaultDeviceAuthFlowScope
} }
} }
if loadedConfig.PKCEAuthorizationFlow != nil { if config.PKCEAuthorizationFlow != nil {
log.Infof("overriding PKCEAuthorizationFlow.TokenEndpoint with a new value: %s, previously configured value: %s", log.Infof("overriding PKCEAuthorizationFlow.TokenEndpoint with a new value: %s, previously configured value: %s",
oidcConfig.TokenEndpoint, loadedConfig.PKCEAuthorizationFlow.ProviderConfig.TokenEndpoint) oidcConfig.TokenEndpoint, config.PKCEAuthorizationFlow.ProviderConfig.TokenEndpoint)
loadedConfig.PKCEAuthorizationFlow.ProviderConfig.TokenEndpoint = oidcConfig.TokenEndpoint config.DeviceAuthorizationFlow.ProviderConfig.TokenEndpoint = oidcConfig.TokenEndpoint
log.Infof("overriding PKCEAuthorizationFlow.AuthorizationEndpoint with a new value: %s, previously configured value: %s", log.Infof("overriding PKCEAuthorizationFlow.AuthorizationEndpoint with a new value: %s, previously configured value: %s",
oidcConfig.AuthorizationEndpoint, loadedConfig.PKCEAuthorizationFlow.ProviderConfig.AuthorizationEndpoint) oidcConfig.AuthorizationEndpoint, config.PKCEAuthorizationFlow.ProviderConfig.AuthorizationEndpoint)
loadedConfig.PKCEAuthorizationFlow.ProviderConfig.AuthorizationEndpoint = oidcConfig.AuthorizationEndpoint config.PKCEAuthorizationFlow.ProviderConfig.AuthorizationEndpoint = oidcConfig.AuthorizationEndpoint
} }
} }
return loadedConfig, err return config, err
} }
// OIDCConfigResponse used for parsing OIDC config response // OIDCConfigResponse used for parsing OIDC config response

View File

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

View File

@@ -216,6 +216,7 @@ 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) {
@@ -1930,120 +1931,6 @@ func TestAccount_GetNextPeerExpiration(t *testing.T) {
} }
} }
func TestAccount_AddJWTGroups(t *testing.T) {
// create a new account
account := &Account{
Peers: map[string]*Peer{
"peer1": {ID: "peer1", Key: "key1", UserID: "user1"},
"peer2": {ID: "peer2", Key: "key2", UserID: "user1"},
"peer3": {ID: "peer3", Key: "key3", UserID: "user1"},
"peer4": {ID: "peer4", Key: "key4", UserID: "user2"},
"peer5": {ID: "peer5", Key: "key5", UserID: "user2"},
},
Groups: map[string]*Group{
"group1": {ID: "group1", Name: "group1", Issued: GroupIssuedAPI, Peers: []string{}},
},
Settings: &Settings{GroupsPropagationEnabled: true},
Users: map[string]*User{
"user1": {Id: "user1"},
"user2": {Id: "user2"},
},
}
t.Run("api group already exists", func(t *testing.T) {
updated := account.AddJWTGroups("user1", []string{"group1"})
assert.False(t, updated, "account should not be updated")
assert.Empty(t, account.Users["user1"].AutoGroups, "auto groups must be empty")
})
t.Run("add jwt group", func(t *testing.T) {
updated := account.AddJWTGroups("user1", []string{"group1", "group2"})
assert.True(t, updated, "account should be updated")
assert.Len(t, account.Groups, 2, "new group should be added")
assert.Len(t, account.Users["user1"].AutoGroups, 1, "new group should be added")
assert.Contains(t, account.Groups, account.Users["user1"].AutoGroups[0], "groups must contain group2 from user groups")
})
t.Run("existed group not update", func(t *testing.T) {
updated := account.AddJWTGroups("user1", []string{"group2"})
assert.False(t, updated, "account should not be updated")
assert.Len(t, account.Groups, 2, "groups count should not be changed")
})
t.Run("add new group", func(t *testing.T) {
updated := account.AddJWTGroups("user2", []string{"group1", "group3"})
assert.True(t, updated, "account should be updated")
assert.Len(t, account.Groups, 3, "new group should be added")
assert.Len(t, account.Users["user2"].AutoGroups, 1, "new group should be added")
assert.Contains(t, account.Groups, account.Users["user2"].AutoGroups[0], "groups must contain group3 from user groups")
})
}
func TestAccount_UserGroupsAddToPeers(t *testing.T) {
account := &Account{
Peers: map[string]*Peer{
"peer1": {ID: "peer1", Key: "key1", UserID: "user1"},
"peer2": {ID: "peer2", Key: "key2", UserID: "user1"},
"peer3": {ID: "peer3", Key: "key3", UserID: "user1"},
"peer4": {ID: "peer4", Key: "key4", UserID: "user2"},
"peer5": {ID: "peer5", Key: "key5", UserID: "user2"},
},
Groups: map[string]*Group{
"group1": {ID: "group1", Name: "group1", Issued: GroupIssuedAPI, Peers: []string{}},
"group2": {ID: "group2", Name: "group2", Issued: GroupIssuedAPI, Peers: []string{}},
"group3": {ID: "group3", Name: "group3", Issued: GroupIssuedAPI, Peers: []string{}},
},
Users: map[string]*User{"user1": {Id: "user1"}, "user2": {Id: "user2"}},
}
t.Run("add groups", func(t *testing.T) {
account.UserGroupsAddToPeers("user1", "group1", "group2")
assert.ElementsMatch(t, account.Groups["group1"].Peers, []string{"peer1", "peer2", "peer3"}, "group1 contains users peers")
assert.ElementsMatch(t, account.Groups["group2"].Peers, []string{"peer1", "peer2", "peer3"}, "group2 contains users peers")
})
t.Run("add same groups", func(t *testing.T) {
account.UserGroupsAddToPeers("user1", "group1", "group2")
assert.Len(t, account.Groups["group1"].Peers, 3, "peers amount in group1 didn't change")
assert.Len(t, account.Groups["group2"].Peers, 3, "peers amount in group2 didn't change")
})
t.Run("add second user peers", func(t *testing.T) {
account.UserGroupsAddToPeers("user2", "group2")
assert.ElementsMatch(t, account.Groups["group2"].Peers,
[]string{"peer1", "peer2", "peer3", "peer4", "peer5"}, "group2 contains first and second user peers")
})
}
func TestAccount_UserGroupsRemoveFromPeers(t *testing.T) {
account := &Account{
Peers: map[string]*Peer{
"peer1": {ID: "peer1", Key: "key1", UserID: "user1"},
"peer2": {ID: "peer2", Key: "key2", UserID: "user1"},
"peer3": {ID: "peer3", Key: "key3", UserID: "user1"},
"peer4": {ID: "peer4", Key: "key4", UserID: "user2"},
"peer5": {ID: "peer5", Key: "key5", UserID: "user2"},
},
Groups: map[string]*Group{
"group1": {ID: "group1", Name: "group1", Issued: GroupIssuedAPI, Peers: []string{"peer1", "peer2", "peer3"}},
"group2": {ID: "group2", Name: "group2", Issued: GroupIssuedAPI, Peers: []string{"peer1", "peer2", "peer3", "peer4", "peer5"}},
"group3": {ID: "group3", Name: "group3", Issued: GroupIssuedAPI, Peers: []string{"peer4", "peer5"}},
},
Users: map[string]*User{"user1": {Id: "user1"}, "user2": {Id: "user2"}},
}
t.Run("remove groups", func(t *testing.T) {
account.UserGroupsRemoveFromPeers("user1", "group1", "group2")
assert.Empty(t, account.Groups["group1"].Peers, "remove all peers from group1")
assert.ElementsMatch(t, account.Groups["group2"].Peers, []string{"peer4", "peer5"}, "group2 contains only second users peers")
})
t.Run("remove group with no peers", func(t *testing.T) {
account.UserGroupsRemoveFromPeers("user1", "group3")
assert.Len(t, account.Groups["group3"].Peers, 2, "peers amount should not change")
})
}
func createManager(t *testing.T) (*DefaultAccountManager, error) { func createManager(t *testing.T) (*DefaultAccountManager, error) {
store, err := createStore(t) store, err := createStore(t)
if err != nil { if err != nil {

View File

@@ -80,14 +80,12 @@ 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
@@ -104,7 +102,6 @@ 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,
}, },

View File

@@ -38,6 +38,7 @@ 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(
@@ -53,6 +54,7 @@ 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")
@@ -92,7 +94,6 @@ 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),
}, },
@@ -109,7 +110,6 @@ 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,30 +126,12 @@ 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,

View File

@@ -54,10 +54,6 @@ 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
@@ -331,7 +327,7 @@ components:
type: string type: string
example: valid example: valid
auto_groups: auto_groups:
description: List of group IDs to auto-assign to peers registered with this key description: Setup key groups to auto-assign to peers registered with this key
type: array type: array
items: items:
type: string type: string
@@ -373,15 +369,13 @@ components:
expires_in: expires_in:
description: Expiration time in seconds description: Expiration time in seconds
type: integer type: integer
minimum: 86400 example: 43200
maximum: 31536000
example: 86400
revoked: revoked:
description: Setup key revocation status description: Setup key revocation status
type: boolean type: boolean
example: false example: false
auto_groups: auto_groups:
description: List of group IDs to auto-assign to peers registered with this key description: Setup key groups to auto-assign to peers registered with this key
type: array type: array
items: items:
type: string type: string

View File

@@ -129,9 +129,6 @@ 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"`
@@ -684,7 +681,7 @@ type RuleRequest struct {
// SetupKey defines model for SetupKey. // SetupKey defines model for SetupKey.
type SetupKey struct { type SetupKey struct {
// AutoGroups List of group IDs to auto-assign to peers registered with this key // AutoGroups Setup key groups to auto-assign to peers registered with this key
AutoGroups []string `json:"auto_groups"` AutoGroups []string `json:"auto_groups"`
// Expires Setup Key expiration date // Expires Setup Key expiration date
@@ -726,7 +723,7 @@ type SetupKey struct {
// SetupKeyRequest defines model for SetupKeyRequest. // SetupKeyRequest defines model for SetupKeyRequest.
type SetupKeyRequest struct { type SetupKeyRequest struct {
// AutoGroups List of group IDs to auto-assign to peers registered with this key // AutoGroups Setup key groups to auto-assign to peers registered with this key
AutoGroups []string `json:"auto_groups"` AutoGroups []string `json:"auto_groups"`
// ExpiresIn Expiration time in seconds // ExpiresIn Expiration time in seconds

View File

@@ -60,13 +60,6 @@ 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{}
} }

View File

@@ -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\",\"expires_in\":86400}", newSetupKey.Name, newSetupKey.Type))), []byte(fmt.Sprintf("{\"name\":\"%s\",\"type\":\"%s\"}", newSetupKey.Name, newSetupKey.Type))),
expectedStatus: http.StatusOK, expectedStatus: http.StatusOK,
expectedBody: true, expectedBody: true,
expectedSetupKey: toResponseBody(newSetupKey), expectedSetupKey: toResponseBody(newSetupKey),

View File

@@ -260,6 +260,7 @@ 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.
@@ -599,13 +600,6 @@ func (am *DefaultAccountManager) SaveUser(accountID, initiatorUserID string, upd
} }
} }
if update.AutoGroups != nil && account.Settings.GroupsPropagationEnabled {
removedGroups := difference(oldUser.AutoGroups, update.AutoGroups)
// need force update all auto groups in any case they will not be dublicated
account.UserGroupsAddToPeers(oldUser.Id, update.AutoGroups...)
account.UserGroupsRemoveFromPeers(oldUser.Id, removedGroups...)
}
if err = am.Store.SaveAccount(account); err != nil { if err = am.Store.SaveAccount(account); err != nil {
return nil, err return nil, err
} }
@@ -646,6 +640,7 @@ func (am *DefaultAccountManager) SaveUser(accountID, initiatorUserID string, upd
} }
} }
} }
}() }()
if !isNil(am.idpManager) && !newUser.IsServiceUser { if !isNil(am.idpManager) && !newUser.IsServiceUser {