Merge branch 'main' into feature/buf-cli

# Conflicts:
#	management/proto/management.pb.go
This commit is contained in:
Pedro Costa
2025-04-17 11:53:47 +01:00
108 changed files with 3378 additions and 2010 deletions
+4
View File
@@ -2,6 +2,10 @@
## Issue ticket number and link ## Issue ticket number and link
## Stack
<!-- branch-stack -->
### Checklist ### Checklist
- [ ] Is it a bug fix - [ ] Is it a bug fix
- [ ] Is a typo/documentation fix - [ ] Is a typo/documentation fix
@@ -178,6 +178,7 @@ jobs:
grep -A 10 'relay:' docker-compose.yml | egrep 'NB_AUTH_SECRET=.+$' grep -A 10 'relay:' docker-compose.yml | egrep 'NB_AUTH_SECRET=.+$'
grep -A 7 Relay management.json | grep "rel://$CI_NETBIRD_DOMAIN:33445" grep -A 7 Relay management.json | grep "rel://$CI_NETBIRD_DOMAIN:33445"
grep -A 7 Relay management.json | egrep '"Secret": ".+"' grep -A 7 Relay management.json | egrep '"Secret": ".+"'
grep DisablePromptLogin management.json | grep 'true'
- name: Install modules - name: Install modules
run: go mod tidy run: go mod tidy
+10 -10
View File
@@ -57,16 +57,16 @@
### Key features ### Key features
| Connectivity | Management | Security | Automation | Platforms | | Connectivity | Management | Security | Automation| Platforms |
|------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------| |----|----|----|----|----|
| <ul><li> - \[x] Kernel WireGuard </ul></li> | <ul><li> - \[x] [Admin Web UI](https://github.com/netbirdio/dashboard) </ul></li> | <ul><li> - \[x] [SSO & MFA support](https://docs.netbird.io/how-to/installation#running-net-bird-with-sso-login) </ul></li> | <ul><li> - \[x] [Public API](https://docs.netbird.io/api) </ul></li> | <ul><li> - \[x] Linux </ul></li> | | <ul><li>- \[x] Kernel WireGuard</ul></li> | <ul><li>- \[x] [Admin Web UI](https://github.com/netbirdio/dashboard)</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] [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] [Access control - groups & rules](https://docs.netbird.io/how-to/manage-network-access) </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 connections</ul></li> | <ul><li>- \[x] Auto peer discovery and configuration</ui></li> | <ul><li>- \[x] [Access control - groups & rules](https://docs.netbird.io/how-to/manage-network-access)</ui></li> | <ul><li>- \[x] [Setup keys for bulk network provisioning](https://docs.netbird.io/how-to/register-machines-using-setup-keys)</ui></li> | <ul><li>- \[x] Mac</ui></li> |
| <ul><li> - \[x] Connection relay fallback </ul></li> | <ul><li> - \[x] [IdP integrations](https://docs.netbird.io/selfhosted/identity-providers) </ul></li> | <ul><li> - \[x] [Activity logging](https://docs.netbird.io/how-to/monitor-system-and-network-activity) </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</ui></li> | <ul><li>- \[x] [IdP integrations](https://docs.netbird.io/selfhosted/identity-providers)</ui></li> | <ul><li>- \[x] [Activity logging](https://docs.netbird.io/how-to/monitor-system-and-network-activity)</ui></li> | <ul><li>- \[x] [Self-hosting quickstart script](https://docs.netbird.io/selfhosted/selfhosted-quickstart)</ui></li> | <ul><li>- \[x] Windows</ui></li> |
| <ul><li> - \[x] [Routes to external networks](https://docs.netbird.io/how-to/routing-traffic-to-private-networks) </ul></li> | <ul><li> - \[x] [Private DNS](https://docs.netbird.io/how-to/manage-dns-in-your-network) </ul></li> | <ul><li> - \[x] [Device posture checks](https://docs.netbird.io/how-to/manage-posture-checks) </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)</ui></li> | <ul><li>- \[x] [Private DNS](https://docs.netbird.io/how-to/manage-dns-in-your-network)</ui></li> | <ul><li>- \[x] [Device posture checks](https://docs.netbird.io/how-to/manage-posture-checks)</ui></li> | <ul><li>- \[x] IdP groups sync with JWT</ui></li> | <ul><li>- \[x] Android</ui></li> |
| <ul><li> - \[x] NAT traversal with BPF </ul></li> | <ul><li> - \[x] [Multiuser support](https://docs.netbird.io/how-to/add-users-to-your-network) </ul></li> | <ul><li> - \[x] Peer-to-peer encryption </ul></li> | | <ul><li> - \[x] iOS </ul></li> | | <ul><li>- \[x] NAT traversal with BPF</ui></li> | <ul><li>- \[x] [Multiuser support](https://docs.netbird.io/how-to/add-users-to-your-network)</ui></li> | <ul><li>- \[x] Peer-to-peer encryption</ui></li> || <ul><li>- \[x] iOS</ui></li> |
| | | <ul><li> - \[x] [Quantum-resistance with Rosenpass](https://netbird.io/knowledge-hub/the-first-quantum-resistant-mesh-vpn) </ul></li> | | <ul><li> - \[x] OpenWRT </ul></li> | ||| <ul><li>- \[x] [Quantum-resistance with Rosenpass](https://netbird.io/knowledge-hub/the-first-quantum-resistant-mesh-vpn)</ui></li> || <ul><li>- \[x] OpenWRT</ui></li> |
| | | <ui><li> - \[x] [Periodic re-authentication](https://docs.netbird.io/how-to/enforce-periodic-user-authentication)</ul></li> | | <ul><li> - \[x] [Serverless](https://docs.netbird.io/how-to/netbird-on-faas) </ul></li> | ||| <ul><li>- \[x] [Periodic re-authentication](https://docs.netbird.io/how-to/enforce-periodic-user-authentication)</ui></li> || <ul><li>- \[x] [Serverless](https://docs.netbird.io/how-to/netbird-on-faas)</ui></li> |
| | | | | <ul><li> - \[x] Docker </ul></li> | ||||| <ul><li>- \[x] Docker</ui></li> |
### Quickstart with NetBird Cloud ### Quickstart with NetBird Cloud
+34
View File
@@ -11,9 +11,12 @@ import (
"google.golang.org/grpc/status" "google.golang.org/grpc/status"
"github.com/netbirdio/netbird/client/internal" "github.com/netbirdio/netbird/client/internal"
"github.com/netbirdio/netbird/client/internal/debug"
"github.com/netbirdio/netbird/client/internal/peer"
"github.com/netbirdio/netbird/client/proto" "github.com/netbirdio/netbird/client/proto"
"github.com/netbirdio/netbird/client/server" "github.com/netbirdio/netbird/client/server"
nbstatus "github.com/netbirdio/netbird/client/status" nbstatus "github.com/netbirdio/netbird/client/status"
mgmProto "github.com/netbirdio/netbird/management/proto"
) )
const errCloseConnection = "Failed to close connection: %v" const errCloseConnection = "Failed to close connection: %v"
@@ -326,3 +329,34 @@ func formatDuration(d time.Duration) string {
s := d / time.Second s := d / time.Second
return fmt.Sprintf("%02d:%02d:%02d", h, m, s) return fmt.Sprintf("%02d:%02d:%02d", h, m, s)
} }
func generateDebugBundle(config *internal.Config, recorder *peer.Status, connectClient *internal.ConnectClient, logFilePath string) {
var networkMap *mgmProto.NetworkMap
var err error
if connectClient != nil {
networkMap, err = connectClient.GetLatestNetworkMap()
if err != nil {
log.Warnf("Failed to get latest network map: %v", err)
}
}
bundleGenerator := debug.NewBundleGenerator(
debug.GeneratorDependencies{
InternalConfig: config,
StatusRecorder: recorder,
NetworkMap: networkMap,
LogFile: logFilePath,
},
debug.BundleConfig{
IncludeSystemInfo: true,
},
)
path, err := bundleGenerator.Generate()
if err != nil {
log.Errorf("Failed to generate debug bundle: %v", err)
return
}
log.Infof("Generated debug bundle from SIGUSR1 at: %s", path)
}
+39
View File
@@ -0,0 +1,39 @@
//go:build unix
package cmd
import (
"context"
"os"
"os/signal"
"syscall"
log "github.com/sirupsen/logrus"
"github.com/netbirdio/netbird/client/internal"
"github.com/netbirdio/netbird/client/internal/peer"
)
func SetupDebugHandler(
ctx context.Context,
config *internal.Config,
recorder *peer.Status,
connectClient *internal.ConnectClient,
logFilePath string,
) {
usr1Ch := make(chan os.Signal, 1)
signal.Notify(usr1Ch, syscall.SIGUSR1)
go func() {
for {
select {
case <-ctx.Done():
return
case <-usr1Ch:
log.Info("Received SIGUSR1. Triggering debug bundle generation.")
go generateDebugBundle(config, recorder, connectClient, logFilePath)
}
}
}()
}
+126
View File
@@ -0,0 +1,126 @@
package cmd
import (
"context"
"errors"
"os"
"strconv"
"time"
log "github.com/sirupsen/logrus"
"golang.org/x/sys/windows"
"github.com/netbirdio/netbird/client/internal"
"github.com/netbirdio/netbird/client/internal/peer"
)
const (
envListenEvent = "NB_LISTEN_DEBUG_EVENT"
debugTriggerEventName = `Global\NetbirdDebugTriggerEvent`
waitTimeout = 5 * time.Second
)
// SetupDebugHandler sets up a Windows event to listen for a signal to generate a debug bundle.
// Example usage with PowerShell:
// $evt = [System.Threading.EventWaitHandle]::OpenExisting("Global\NetbirdDebugTriggerEvent")
// $evt.Set()
// $evt.Close()
func SetupDebugHandler(
ctx context.Context,
config *internal.Config,
recorder *peer.Status,
connectClient *internal.ConnectClient,
logFilePath string,
) {
env := os.Getenv(envListenEvent)
if env == "" {
return
}
listenEvent, err := strconv.ParseBool(env)
if err != nil {
log.Errorf("Failed to parse %s: %v", envListenEvent, err)
return
}
if !listenEvent {
return
}
eventNamePtr, err := windows.UTF16PtrFromString(debugTriggerEventName)
if err != nil {
log.Errorf("Failed to convert event name '%s' to UTF16: %v", debugTriggerEventName, err)
return
}
// TODO: restrict access by ACL
eventHandle, err := windows.CreateEvent(nil, 1, 0, eventNamePtr)
if err != nil {
if errors.Is(err, windows.ERROR_ALREADY_EXISTS) {
log.Warnf("Debug trigger event '%s' already exists. Attempting to open.", debugTriggerEventName)
// SYNCHRONIZE is needed for WaitForSingleObject, EVENT_MODIFY_STATE for ResetEvent.
eventHandle, err = windows.OpenEvent(windows.SYNCHRONIZE|windows.EVENT_MODIFY_STATE, false, eventNamePtr)
if err != nil {
log.Errorf("Failed to open existing debug trigger event '%s': %v", debugTriggerEventName, err)
return
}
log.Infof("Successfully opened existing debug trigger event '%s'.", debugTriggerEventName)
} else {
log.Errorf("Failed to create debug trigger event '%s': %v", debugTriggerEventName, err)
return
}
}
if eventHandle == windows.InvalidHandle {
log.Errorf("Obtained an invalid handle for debug trigger event '%s'", debugTriggerEventName)
return
}
log.Infof("Debug handler waiting for signal on event: %s", debugTriggerEventName)
go waitForEvent(ctx, config, recorder, connectClient, logFilePath, eventHandle)
}
func waitForEvent(
ctx context.Context,
config *internal.Config,
recorder *peer.Status,
connectClient *internal.ConnectClient,
logFilePath string,
eventHandle windows.Handle,
) {
defer func() {
if err := windows.CloseHandle(eventHandle); err != nil {
log.Errorf("Failed to close debug event handle '%s': %v", debugTriggerEventName, err)
}
}()
for {
if ctx.Err() != nil {
return
}
status, err := windows.WaitForSingleObject(eventHandle, uint32(waitTimeout.Milliseconds()))
switch status {
case windows.WAIT_OBJECT_0:
log.Info("Received signal on debug event. Triggering debug bundle generation.")
// reset the event so it can be triggered again later (manual reset == 1)
if err := windows.ResetEvent(eventHandle); err != nil {
log.Errorf("Failed to reset debug event '%s': %v", debugTriggerEventName, err)
}
go generateDebugBundle(config, recorder, connectClient, logFilePath)
case uint32(windows.WAIT_TIMEOUT):
default:
log.Errorf("Unexpected status %d from WaitForSingleObject for debug event '%s': %v", status, debugTriggerEventName, err)
select {
case <-time.After(5 * time.Second):
case <-ctx.Done():
return
}
}
}
}
+1
View File
@@ -115,6 +115,7 @@ var runCmd = &cobra.Command{
ctx, cancel := context.WithCancel(cmd.Context()) ctx, cancel := context.WithCancel(cmd.Context())
SetupCloseHandler(ctx, cancel) SetupCloseHandler(ctx, cancel)
SetupDebugHandler(ctx, nil, nil, nil, logFile)
s, err := newSVC(newProgram(ctx, cancel), newSVCConfig()) s, err := newSVC(newProgram(ctx, cancel), newSVCConfig())
if err != nil { if err != nil {
+1 -1
View File
@@ -92,11 +92,11 @@ func startManagement(t *testing.T, config *types.Config, testFile string) (*grpc
metrics, err := telemetry.NewDefaultAppMetrics(context.Background()) metrics, err := telemetry.NewDefaultAppMetrics(context.Background())
require.NoError(t, err) require.NoError(t, err)
permissionsManagerMock := permissions.NewManagerMock()
ctrl := gomock.NewController(t) ctrl := gomock.NewController(t)
t.Cleanup(ctrl.Finish) t.Cleanup(ctrl.Finish)
settingsMockManager := settings.NewMockManager(ctrl) settingsMockManager := settings.NewMockManager(ctrl)
permissionsManagerMock := permissions.NewMockManager(ctrl)
accountManager, err := mgmt.BuildManager(context.Background(), store, peersUpdateManager, nil, "", "netbird.selfhosted", eventStore, nil, false, iv, metrics, port_forwarding.NewControllerMock(), settingsMockManager, permissionsManagerMock) accountManager, err := mgmt.BuildManager(context.Background(), store, peersUpdateManager, nil, "", "netbird.selfhosted", eventStore, nil, false, iv, metrics, port_forwarding.NewControllerMock(), settingsMockManager, permissionsManagerMock)
if err != nil { if err != nil {
+2
View File
@@ -219,6 +219,8 @@ func runInForegroundMode(ctx context.Context, cmd *cobra.Command) error {
r.GetFullStatus() r.GetFullStatus()
connectClient := internal.NewConnectClient(ctx, config, r) connectClient := internal.NewConnectClient(ctx, config, r)
SetupDebugHandler(ctx, config, r, connectClient, "")
return connectClient.Run(nil) return connectClient.Run(nil)
} }
+1 -1
View File
@@ -458,6 +458,6 @@ func newBufferHolder(size int) *bufferHolder {
func getLogger() logging.LeveledLogger { func getLogger() logging.LeveledLogger {
fac := logging.NewDefaultLoggerFactory() fac := logging.NewDefaultLoggerFactory()
fac.Writer = log.StandardLogger().Writer() //fac.Writer = log.StandardLogger().Writer()
return fac.NewLogger("ice") return fac.NewLogger("ice")
} }
+8 -4
View File
@@ -94,13 +94,17 @@ func (p *PKCEAuthorizationFlow) RequestAuthInfo(ctx context.Context) (AuthFlowIn
p.codeVerifier = codeVerifier p.codeVerifier = codeVerifier
codeChallenge := createCodeChallenge(codeVerifier) codeChallenge := createCodeChallenge(codeVerifier)
authURL := p.oAuthConfig.AuthCodeURL(
state, params := []oauth2.AuthCodeOption{
oauth2.SetAuthURLParam("code_challenge_method", "S256"), oauth2.SetAuthURLParam("code_challenge_method", "S256"),
oauth2.SetAuthURLParam("code_challenge", codeChallenge), oauth2.SetAuthURLParam("code_challenge", codeChallenge),
oauth2.SetAuthURLParam("audience", p.providerConfig.Audience), oauth2.SetAuthURLParam("audience", p.providerConfig.Audience),
oauth2.SetAuthURLParam("prompt", "login"), }
) if !p.providerConfig.DisablePromptLogin {
params = append(params, oauth2.SetAuthURLParam("prompt", "login"))
}
authURL := p.oAuthConfig.AuthCodeURL(state, params...)
return AuthFlowInfo{ return AuthFlowInfo{
VerificationURIComplete: authURL, VerificationURIComplete: authURL,
+49
View File
@@ -0,0 +1,49 @@
package auth
import (
"context"
"testing"
"github.com/stretchr/testify/require"
"github.com/netbirdio/netbird/client/internal"
)
func TestPromptLogin(t *testing.T) {
tt := []struct {
name string
prompt bool
}{
{"PromptLogin", true},
{"NoPromptLogin", false},
}
for _, tc := range tt {
t.Run(tc.name, func(t *testing.T) {
config := internal.PKCEAuthProviderConfig{
ClientID: "test-client-id",
Audience: "test-audience",
TokenEndpoint: "https://test-token-endpoint.com/token",
Scope: "openid email profile",
AuthorizationEndpoint: "https://test-auth-endpoint.com/authorize",
RedirectURLs: []string{"http://127.0.0.1:33992/"},
UseIDToken: true,
DisablePromptLogin: !tc.prompt,
}
pkce, err := NewPKCEAuthorizationFlow(config)
if err != nil {
t.Fatalf("Failed to create PKCEAuthorizationFlow: %v", err)
}
authInfo, err := pkce.RequestAuthInfo(context.Background())
if err != nil {
t.Fatalf("Failed to request auth info: %v", err)
}
pattern := "prompt=login"
if tc.prompt {
require.Contains(t, authInfo.VerificationURIComplete, pattern)
} else {
require.NotContains(t, authInfo.VerificationURIComplete, pattern)
}
})
}
}
+19
View File
@@ -349,6 +349,25 @@ func (c *ConnectClient) Engine() *Engine {
return e return e
} }
// GetLatestNetworkMap returns the latest network map from the engine.
func (c *ConnectClient) GetLatestNetworkMap() (*mgmProto.NetworkMap, error) {
engine := c.Engine()
if engine == nil {
return nil, errors.New("engine is not initialized")
}
networkMap, err := engine.GetLatestNetworkMap()
if err != nil {
return nil, fmt.Errorf("get latest network map: %w", err)
}
if networkMap == nil {
return nil, errors.New("network map is not available")
}
return networkMap, nil
}
// Status returns the current client status // Status returns the current client status
func (c *ConnectClient) Status() StatusType { func (c *ConnectClient) Status() StatusType {
if c == nil { if c == nil {
File diff suppressed because it is too large Load Diff
@@ -1,9 +1,8 @@
//go:build linux && !android //go:build linux && !android
package server package debug
import ( import (
"archive/zip"
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"fmt" "fmt"
@@ -14,36 +13,31 @@ import (
"github.com/google/nftables" "github.com/google/nftables"
"github.com/google/nftables/expr" "github.com/google/nftables/expr"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/netbirdio/netbird/client/anonymize"
"github.com/netbirdio/netbird/client/proto"
) )
// addFirewallRules collects and adds firewall rules to the archive // addFirewallRules collects and adds firewall rules to the archive
func (s *Server) addFirewallRules(req *proto.DebugBundleRequest, anonymizer *anonymize.Anonymizer, archive *zip.Writer) error { func (g *BundleGenerator) addFirewallRules() error {
log.Info("Collecting firewall rules") log.Info("Collecting firewall rules")
// Collect and add iptables rules
iptablesRules, err := collectIPTablesRules() iptablesRules, err := collectIPTablesRules()
if err != nil { if err != nil {
log.Warnf("Failed to collect iptables rules: %v", err) log.Warnf("Failed to collect iptables rules: %v", err)
} else { } else {
if req.GetAnonymize() { if g.anonymize {
iptablesRules = anonymizer.AnonymizeString(iptablesRules) iptablesRules = g.anonymizer.AnonymizeString(iptablesRules)
} }
if err := addFileToZip(archive, strings.NewReader(iptablesRules), "iptables.txt"); err != nil { if err := g.addFileToZip(strings.NewReader(iptablesRules), "iptables.txt"); err != nil {
log.Warnf("Failed to add iptables rules to bundle: %v", err) log.Warnf("Failed to add iptables rules to bundle: %v", err)
} }
} }
// Collect and add nftables rules
nftablesRules, err := collectNFTablesRules() nftablesRules, err := collectNFTablesRules()
if err != nil { if err != nil {
log.Warnf("Failed to collect nftables rules: %v", err) log.Warnf("Failed to collect nftables rules: %v", err)
} else { } else {
if req.GetAnonymize() { if g.anonymize {
nftablesRules = anonymizer.AnonymizeString(nftablesRules) nftablesRules = g.anonymizer.AnonymizeString(nftablesRules)
} }
if err := addFileToZip(archive, strings.NewReader(nftablesRules), "nftables.txt"); err != nil { if err := g.addFileToZip(strings.NewReader(nftablesRules), "nftables.txt"); err != nil {
log.Warnf("Failed to add nftables rules to bundle: %v", err) log.Warnf("Failed to add nftables rules to bundle: %v", err)
} }
} }
@@ -65,16 +59,13 @@ func collectIPTablesRules() (string, error) {
builder.WriteString("\n") builder.WriteString("\n")
} }
// Then get verbose statistics for each table
builder.WriteString("=== iptables -v -n -L output ===\n") builder.WriteString("=== iptables -v -n -L output ===\n")
// Get list of tables
tables := []string{"filter", "nat", "mangle", "raw", "security"} tables := []string{"filter", "nat", "mangle", "raw", "security"}
for _, table := range tables { for _, table := range tables {
builder.WriteString(fmt.Sprintf("*%s\n", table)) builder.WriteString(fmt.Sprintf("*%s\n", table))
// Get verbose statistics for the entire table
stats, err := getTableStatistics(table) stats, err := getTableStatistics(table)
if err != nil { if err != nil {
log.Warnf("Failed to get statistics for table %s: %v", table, err) log.Warnf("Failed to get statistics for table %s: %v", table, err)
@@ -182,12 +173,10 @@ func formatTables(conn *nftables.Conn, tables []*nftables.Table) string {
continue continue
} }
// Format chains
for _, chain := range chains { for _, chain := range chains {
formatChain(conn, table, chain, &builder) formatChain(conn, table, chain, &builder)
} }
// Format sets
if sets, err := conn.GetSets(table); err != nil { if sets, err := conn.GetSets(table); err != nil {
log.Warnf("Failed to get sets for table %s: %v", table.Name, err) log.Warnf("Failed to get sets for table %s: %v", table.Name, err)
} else if len(sets) > 0 { } else if len(sets) > 0 {
+7
View File
@@ -0,0 +1,7 @@
//go:build ios || android
package debug
func (g *BundleGenerator) addRoutes() error {
return nil
}
+8
View File
@@ -0,0 +1,8 @@
//go:build !linux || android
package debug
// collectFirewallRules returns nothing on non-linux systems
func (g *BundleGenerator) addFirewallRules() error {
return nil
}
+25
View File
@@ -0,0 +1,25 @@
//go:build !ios && !android
package debug
import (
"fmt"
"strings"
"github.com/netbirdio/netbird/client/internal/routemanager/systemops"
)
func (g *BundleGenerator) addRoutes() error {
routes, err := systemops.GetRoutesFromTable()
if err != nil {
return fmt.Errorf("get routes: %w", err)
}
// TODO: get routes including nexthop
routesContent := formatRoutes(routes, g.anonymize, g.anonymizer)
routesReader := strings.NewReader(routesContent)
if err := g.addFileToZip(routesReader, "routes.txt"); err != nil {
return fmt.Errorf("add routes file to zip: %w", err)
}
return nil
}
@@ -1,4 +1,4 @@
package server package debug
import ( import (
"encoding/json" "encoding/json"
+6
View File
@@ -71,6 +71,12 @@ func (d *localResolver) lookupRecords(r *dns.Msg) []dns.RR {
value, found := d.records.Load(key) value, found := d.records.Load(key)
if !found { if !found {
// alternatively check if we have a cname
if question.Qtype != dns.TypeCNAME {
r.Question[0].Qtype = dns.TypeCNAME
return d.lookupRecords(r)
}
return nil return nil
} }
+5
View File
@@ -467,6 +467,11 @@ func (s *DefaultServer) applyHostConfig() {
return return
} }
// prevent reapplying config if we're shutting down
if s.ctx.Err() != nil {
return
}
config := s.currentConfig config := s.currentConfig
existingDomains := make(map[string]struct{}) existingDomains := make(map[string]struct{})
+34 -4
View File
@@ -3,6 +3,7 @@ package dnsfwd
import ( import (
"context" "context"
"errors" "errors"
"math"
"net" "net"
"net/netip" "net/netip"
"strings" "strings"
@@ -62,7 +63,6 @@ func (f *DNSForwarder) UpdateDomains(domains []string, resIds map[string]string)
for _, d := range f.domains { for _, d := range f.domains {
f.mux.HandleRemove(d) f.mux.HandleRemove(d)
f.statusRecorder.RemoveResolvedIPLookupEntry(d)
} }
f.resId.Clear() f.resId.Clear()
@@ -122,8 +122,8 @@ func (f *DNSForwarder) handleDNSQuery(w dns.ResponseWriter, query *dns.Msg) {
return return
} }
resId, ok := f.resId.Load(strings.TrimSuffix(domain, ".")) resId := f.getResIdForDomain(strings.TrimSuffix(domain, "."))
if ok { if resId != "" {
for _, ip := range ips { for _, ip := range ips {
var ipWithSuffix string var ipWithSuffix string
if ip.Is4() { if ip.Is4() {
@@ -133,7 +133,7 @@ func (f *DNSForwarder) handleDNSQuery(w dns.ResponseWriter, query *dns.Msg) {
ipWithSuffix = ip.String() + "/128" ipWithSuffix = ip.String() + "/128"
log.Tracef("resolved domain=%s to IPv6=%s", domain, ipWithSuffix) log.Tracef("resolved domain=%s to IPv6=%s", domain, ipWithSuffix)
} }
f.statusRecorder.AddResolvedIPLookupEntry(ipWithSuffix, resId.(string)) f.statusRecorder.AddResolvedIPLookupEntry(ipWithSuffix, resId)
} }
} }
@@ -204,6 +204,36 @@ func (f *DNSForwarder) addIPsToResponse(resp *dns.Msg, domain string, ips []neti
} }
} }
func (f *DNSForwarder) getResIdForDomain(domain string) string {
var selectedResId string
var bestScore int
f.resId.Range(func(key, value interface{}) bool {
var score int
pattern := key.(string)
switch {
case strings.HasPrefix(pattern, "*."):
baseDomain := strings.TrimPrefix(pattern, "*.")
if domain == baseDomain || strings.HasSuffix(domain, "."+baseDomain) {
score = len(baseDomain)
}
case domain == pattern:
score = math.MaxInt
default:
return true
}
if score > bestScore {
bestScore = score
selectedResId = value.(string)
}
return true
})
return selectedResId
}
// filterDomains returns a list of normalized domains // filterDomains returns a list of normalized domains
func filterDomains(domains []string) []string { func filterDomains(domains []string) []string {
newDomains := make([]string, 0, len(domains)) newDomains := make([]string, 0, len(domains))
+95
View File
@@ -0,0 +1,95 @@
package dnsfwd
import (
"sync"
"testing"
)
func TestGetResIdForDomain(t *testing.T) {
testCases := []struct {
name string
storedMappings map[string]string // key: domain pattern, value: resId
queryDomain string
expectedResId string
}{
{
name: "Empty map returns empty string",
storedMappings: map[string]string{},
queryDomain: "example.com",
expectedResId: "",
},
{
name: "Exact match returns stored resId",
storedMappings: map[string]string{"example.com": "res1"},
queryDomain: "example.com",
expectedResId: "res1",
},
{
name: "Wildcard pattern matches base domain",
storedMappings: map[string]string{"*.example.com": "res2"},
queryDomain: "example.com",
expectedResId: "res2",
},
{
name: "Wildcard pattern matches subdomain",
storedMappings: map[string]string{"*.example.com": "res3"},
queryDomain: "foo.example.com",
expectedResId: "res3",
},
{
name: "Wildcard pattern does not match different domain",
storedMappings: map[string]string{"*.example.com": "res4"},
queryDomain: "foo.notexample.com",
expectedResId: "",
},
{
name: "Non-wildcard pattern does not match subdomain",
storedMappings: map[string]string{"example.com": "res5"},
queryDomain: "foo.example.com",
expectedResId: "",
},
{
name: "Exact match over overlapping wildcard",
storedMappings: map[string]string{
"*.example.com": "resWildcard",
"foo.example.com": "resExact",
},
queryDomain: "foo.example.com",
expectedResId: "resExact",
},
{
name: "Overlapping wildcards: Select more specific wildcard",
storedMappings: map[string]string{
"*.example.com": "resA",
"*.sub.example.com": "resB",
},
queryDomain: "bar.sub.example.com",
expectedResId: "resB",
},
{
name: "Wildcard multi-level subdomain match",
storedMappings: map[string]string{
"*.example.com": "resMulti",
},
queryDomain: "a.b.example.com",
expectedResId: "resMulti",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
fwd := &DNSForwarder{
resId: sync.Map{},
}
for domainPattern, resId := range tc.storedMappings {
fwd.resId.Store(domainPattern, resId)
}
got := fwd.getResIdForDomain(tc.queryDomain)
if got != tc.expectedResId {
t.Errorf("For query domain %q, expected resId %q, but got %q", tc.queryDomain, tc.expectedResId, got)
}
})
}
}
+10 -27
View File
@@ -1231,36 +1231,19 @@ func (e *Engine) createPeerConn(pubKey string, allowedIPs []netip.Prefix) (*peer
PreSharedKey: e.config.PreSharedKey, PreSharedKey: e.config.PreSharedKey,
} }
if e.config.RosenpassEnabled && !e.config.RosenpassPermissive {
lk := []byte(e.config.WgPrivateKey.PublicKey().String())
rk := []byte(wgConfig.RemoteKey)
var keyInput []byte
if string(lk) > string(rk) {
//nolint:gocritic
keyInput = append(lk[:16], rk[:16]...)
} else {
//nolint:gocritic
keyInput = append(rk[:16], lk[:16]...)
}
key, err := wgtypes.NewKey(keyInput)
if err != nil {
return nil, err
}
wgConfig.PreSharedKey = &key
}
// randomize connection timeout // randomize connection timeout
timeout := time.Duration(rand.Intn(PeerConnectionTimeoutMax-PeerConnectionTimeoutMin)+PeerConnectionTimeoutMin) * time.Millisecond timeout := time.Duration(rand.Intn(PeerConnectionTimeoutMax-PeerConnectionTimeoutMin)+PeerConnectionTimeoutMin) * time.Millisecond
config := peer.ConnConfig{ config := peer.ConnConfig{
Key: pubKey, Key: pubKey,
LocalKey: e.config.WgPrivateKey.PublicKey().String(), LocalKey: e.config.WgPrivateKey.PublicKey().String(),
Timeout: timeout, Timeout: timeout,
WgConfig: wgConfig, WgConfig: wgConfig,
LocalWgPort: e.config.WgPort, LocalWgPort: e.config.WgPort,
RosenpassPubKey: e.getRosenpassPubKey(), RosenpassConfig: peer.RosenpassConfig{
RosenpassAddr: e.getRosenpassAddr(), PubKey: e.getRosenpassPubKey(),
Addr: e.getRosenpassAddr(),
PermissiveMode: e.config.RosenpassPermissive,
},
ICEConfig: icemaker.Config{ ICEConfig: icemaker.Config{
StunTurn: &e.stunTurn, StunTurn: &e.stunTurn,
InterfaceBlackList: e.config.IFaceBlackList, InterfaceBlackList: e.config.IFaceBlackList,
+3 -3
View File
@@ -1439,8 +1439,6 @@ func startManagement(t *testing.T, dataDir, testFile string) (*grpc.Server, stri
metrics, err := telemetry.NewDefaultAppMetrics(context.Background()) metrics, err := telemetry.NewDefaultAppMetrics(context.Background())
require.NoError(t, err) require.NoError(t, err)
permissionsManagerMock := permissions.NewManagerMock()
ctrl := gomock.NewController(t) ctrl := gomock.NewController(t)
t.Cleanup(ctrl.Finish) t.Cleanup(ctrl.Finish)
settingsMockManager := settings.NewMockManager(ctrl) settingsMockManager := settings.NewMockManager(ctrl)
@@ -1449,7 +1447,9 @@ func startManagement(t *testing.T, dataDir, testFile string) (*grpc.Server, stri
Return(&types.Settings{}, nil). Return(&types.Settings{}, nil).
AnyTimes() AnyTimes()
accountManager, err := server.BuildManager(context.Background(), store, peersUpdateManager, nil, "", "netbird.selfhosted", eventStore, nil, false, ia, metrics, port_forwarding.NewControllerMock(), settingsMockManager, permissionsManagerMock) permissionsManager := permissions.NewManager(store)
accountManager, err := server.BuildManager(context.Background(), store, peersUpdateManager, nil, "", "netbird.selfhosted", eventStore, nil, false, ia, metrics, port_forwarding.NewControllerMock(), settingsMockManager, permissionsManager)
if err != nil { if err != nil {
return nil, "", err return nil, "", err
} }
+57 -9
View File
@@ -60,6 +60,15 @@ type WgConfig struct {
PreSharedKey *wgtypes.Key PreSharedKey *wgtypes.Key
} }
type RosenpassConfig struct {
// RosenpassPubKey is this peer's Rosenpass public key
PubKey []byte
// RosenpassPubKey is this peer's RosenpassAddr server address (IP:port)
Addr string
PermissiveMode bool
}
// ConnConfig is a peer Connection configuration // ConnConfig is a peer Connection configuration
type ConnConfig struct { type ConnConfig struct {
// Key is a public key of a remote peer // Key is a public key of a remote peer
@@ -73,10 +82,7 @@ type ConnConfig struct {
LocalWgPort int LocalWgPort int
// RosenpassPubKey is this peer's Rosenpass public key RosenpassConfig RosenpassConfig
RosenpassPubKey []byte
// RosenpassPubKey is this peer's RosenpassAddr server address (IP:port)
RosenpassAddr string
// ICEConfig ICE protocol configuration // ICEConfig ICE protocol configuration
ICEConfig icemaker.Config ICEConfig icemaker.Config
@@ -109,6 +115,8 @@ type Conn struct {
connIDICE nbnet.ConnectionID connIDICE nbnet.ConnectionID
beforeAddPeerHooks []nbnet.AddHookFunc beforeAddPeerHooks []nbnet.AddHookFunc
afterRemovePeerHooks []nbnet.RemoveHookFunc afterRemovePeerHooks []nbnet.RemoveHookFunc
// used to store the remote Rosenpass key for Relayed connection in case of connection update from ice
rosenpassRemoteKey []byte
wgProxyICE wgproxy.Proxy wgProxyICE wgproxy.Proxy
wgProxyRelay wgproxy.Proxy wgProxyRelay wgproxy.Proxy
@@ -375,7 +383,7 @@ func (conn *Conn) onICEConnectionIsReady(priority ConnPriority, iceConnInfo ICEC
wgProxy.Work() wgProxy.Work()
} }
if err = conn.configureWGEndpoint(ep); err != nil { if err = conn.configureWGEndpoint(ep, iceConnInfo.RosenpassPubKey); err != nil {
conn.handleConfigurationFailure(err, wgProxy) conn.handleConfigurationFailure(err, wgProxy)
return return
} }
@@ -408,7 +416,7 @@ func (conn *Conn) onICEStateDisconnected() {
conn.dumpState.SwitchToRelay() conn.dumpState.SwitchToRelay()
conn.wgProxyRelay.Work() conn.wgProxyRelay.Work()
if err := conn.configureWGEndpoint(conn.wgProxyRelay.EndpointAddr()); err != nil { if err := conn.configureWGEndpoint(conn.wgProxyRelay.EndpointAddr(), conn.rosenpassRemoteKey); err != nil {
conn.log.Errorf("failed to switch to relay conn: %v", err) conn.log.Errorf("failed to switch to relay conn: %v", err)
} }
@@ -478,7 +486,7 @@ func (conn *Conn) onRelayConnectionIsReady(rci RelayConnInfo) {
} }
wgProxy.Work() wgProxy.Work()
if err := conn.configureWGEndpoint(wgProxy.EndpointAddr()); err != nil { if err := conn.configureWGEndpoint(wgProxy.EndpointAddr(), rci.rosenpassPubKey); err != nil {
if err := wgProxy.CloseConn(); err != nil { if err := wgProxy.CloseConn(); err != nil {
conn.log.Warnf("Failed to close relay connection: %v", err) conn.log.Warnf("Failed to close relay connection: %v", err)
} }
@@ -493,6 +501,7 @@ func (conn *Conn) onRelayConnectionIsReady(rci RelayConnInfo) {
}() }()
wgConfigWorkaround() wgConfigWorkaround()
conn.rosenpassRemoteKey = rci.rosenpassPubKey
conn.currentConnPriority = connPriorityRelay conn.currentConnPriority = connPriorityRelay
conn.statusRelay.Set(StatusConnected) conn.statusRelay.Set(StatusConnected)
conn.setRelayedProxy(wgProxy) conn.setRelayedProxy(wgProxy)
@@ -556,13 +565,14 @@ func (conn *Conn) listenGuardEvent(ctx context.Context) {
} }
} }
func (conn *Conn) configureWGEndpoint(addr *net.UDPAddr) error { func (conn *Conn) configureWGEndpoint(addr *net.UDPAddr, remoteRPKey []byte) error {
presharedKey := conn.presharedKey(remoteRPKey)
return conn.config.WgConfig.WgInterface.UpdatePeer( return conn.config.WgConfig.WgInterface.UpdatePeer(
conn.config.WgConfig.RemoteKey, conn.config.WgConfig.RemoteKey,
conn.config.WgConfig.AllowedIps, conn.config.WgConfig.AllowedIps,
defaultWgKeepAlive, defaultWgKeepAlive,
addr, addr,
conn.config.WgConfig.PreSharedKey, presharedKey,
) )
} }
@@ -783,6 +793,44 @@ func (conn *Conn) AllowedIP() netip.Addr {
return conn.config.WgConfig.AllowedIps[0].Addr() return conn.config.WgConfig.AllowedIps[0].Addr()
} }
func (conn *Conn) presharedKey(remoteRosenpassKey []byte) *wgtypes.Key {
if conn.config.RosenpassConfig.PubKey == nil {
return conn.config.WgConfig.PreSharedKey
}
if remoteRosenpassKey == nil && conn.config.RosenpassConfig.PermissiveMode {
return conn.config.WgConfig.PreSharedKey
}
determKey, err := conn.rosenpassDetermKey()
if err != nil {
conn.log.Errorf("failed to generate Rosenpass initial key: %v", err)
return conn.config.WgConfig.PreSharedKey
}
return determKey
}
// todo: move this logic into Rosenpass package
func (conn *Conn) rosenpassDetermKey() (*wgtypes.Key, error) {
lk := []byte(conn.config.LocalKey)
rk := []byte(conn.config.Key) // remote key
var keyInput []byte
if string(lk) > string(rk) {
//nolint:gocritic
keyInput = append(lk[:16], rk[:16]...)
} else {
//nolint:gocritic
keyInput = append(rk[:16], lk[:16]...)
}
key, err := wgtypes.NewKey(keyInput)
if err != nil {
return nil, err
}
return &key, nil
}
func isController(config ConnConfig) bool { func isController(config ConnConfig) bool {
return config.LocalKey > config.Key return config.LocalKey > config.Key
} }
+143
View File
@@ -2,6 +2,7 @@ package peer
import ( import (
"context" "context"
"fmt"
"os" "os"
"sync" "sync"
"testing" "testing"
@@ -161,3 +162,145 @@ func TestConn_Status(t *testing.T) {
}) })
} }
} }
func TestConn_presharedKey(t *testing.T) {
conn1 := Conn{
config: ConnConfig{
Key: "LLHf3Ma6z6mdLbriAJbqhX7+nM/B71lgw2+91q3LfhU=",
LocalKey: "RRHf3Ma6z6mdLbriAJbqhX7+nM/B71lgw2+91q3LfhU=",
RosenpassConfig: RosenpassConfig{},
},
}
conn2 := Conn{
config: ConnConfig{
Key: "RRHf3Ma6z6mdLbriAJbqhX7+nM/B71lgw2+91q3LfhU=",
LocalKey: "LLHf3Ma6z6mdLbriAJbqhX7+nM/B71lgw2+91q3LfhU=",
RosenpassConfig: RosenpassConfig{},
},
}
tests := []struct {
conn1Permissive bool
conn1RosenpassEnabled bool
conn2Permissive bool
conn2RosenpassEnabled bool
conn1ExpectedInitialKey bool
conn2ExpectedInitialKey bool
}{
{
conn1Permissive: false,
conn1RosenpassEnabled: false,
conn2Permissive: false,
conn2RosenpassEnabled: false,
conn1ExpectedInitialKey: false,
conn2ExpectedInitialKey: false,
},
{
conn1Permissive: false,
conn1RosenpassEnabled: true,
conn2Permissive: false,
conn2RosenpassEnabled: true,
conn1ExpectedInitialKey: true,
conn2ExpectedInitialKey: true,
},
{
conn1Permissive: false,
conn1RosenpassEnabled: true,
conn2Permissive: false,
conn2RosenpassEnabled: false,
conn1ExpectedInitialKey: true,
conn2ExpectedInitialKey: false,
},
{
conn1Permissive: false,
conn1RosenpassEnabled: false,
conn2Permissive: false,
conn2RosenpassEnabled: true,
conn1ExpectedInitialKey: false,
conn2ExpectedInitialKey: true,
},
{
conn1Permissive: true,
conn1RosenpassEnabled: true,
conn2Permissive: false,
conn2RosenpassEnabled: false,
conn1ExpectedInitialKey: false,
conn2ExpectedInitialKey: false,
},
{
conn1Permissive: false,
conn1RosenpassEnabled: false,
conn2Permissive: true,
conn2RosenpassEnabled: true,
conn1ExpectedInitialKey: false,
conn2ExpectedInitialKey: false,
},
{
conn1Permissive: true,
conn1RosenpassEnabled: true,
conn2Permissive: true,
conn2RosenpassEnabled: true,
conn1ExpectedInitialKey: true,
conn2ExpectedInitialKey: true,
},
{
conn1Permissive: false,
conn1RosenpassEnabled: false,
conn2Permissive: false,
conn2RosenpassEnabled: true,
conn1ExpectedInitialKey: false,
conn2ExpectedInitialKey: true,
},
{
conn1Permissive: false,
conn1RosenpassEnabled: true,
conn2Permissive: true,
conn2RosenpassEnabled: true,
conn1ExpectedInitialKey: true,
conn2ExpectedInitialKey: true,
},
}
conn1.config.RosenpassConfig.PermissiveMode = true
for i, test := range tests {
tcase := i + 1
t.Run(fmt.Sprintf("Rosenpass test case %d", tcase), func(t *testing.T) {
conn1.config.RosenpassConfig = RosenpassConfig{}
conn2.config.RosenpassConfig = RosenpassConfig{}
if test.conn1RosenpassEnabled {
conn1.config.RosenpassConfig.PubKey = []byte("dummykey")
}
conn1.config.RosenpassConfig.PermissiveMode = test.conn1Permissive
if test.conn2RosenpassEnabled {
conn2.config.RosenpassConfig.PubKey = []byte("dummykey")
}
conn2.config.RosenpassConfig.PermissiveMode = test.conn2Permissive
conn1PresharedKey := conn1.presharedKey(conn2.config.RosenpassConfig.PubKey)
conn2PresharedKey := conn2.presharedKey(conn1.config.RosenpassConfig.PubKey)
if test.conn1ExpectedInitialKey {
if conn1PresharedKey == nil {
t.Errorf("Case %d: Expected conn1 to have a non-nil key, but got nil", tcase)
}
} else {
if conn1PresharedKey != nil {
t.Errorf("Case %d: Expected conn1 to have a nil key, but got %v", tcase, conn1PresharedKey)
}
}
// Assert conn2's key expectation
if test.conn2ExpectedInitialKey {
if conn2PresharedKey == nil {
t.Errorf("Case %d: Expected conn2 to have a non-nil key, but got nil", tcase)
}
} else {
if conn2PresharedKey != nil {
t.Errorf("Case %d: Expected conn2 to have a nil key, but got %v", tcase, conn2PresharedKey)
}
}
})
}
}
+4 -4
View File
@@ -154,8 +154,8 @@ func (h *Handshaker) sendOffer() error {
IceCredentials: IceCredentials{iceUFrag, icePwd}, IceCredentials: IceCredentials{iceUFrag, icePwd},
WgListenPort: h.config.LocalWgPort, WgListenPort: h.config.LocalWgPort,
Version: version.NetbirdVersion(), Version: version.NetbirdVersion(),
RosenpassPubKey: h.config.RosenpassPubKey, RosenpassPubKey: h.config.RosenpassConfig.PubKey,
RosenpassAddr: h.config.RosenpassAddr, RosenpassAddr: h.config.RosenpassConfig.Addr,
} }
addr, err := h.relay.RelayInstanceAddress() addr, err := h.relay.RelayInstanceAddress()
@@ -174,8 +174,8 @@ func (h *Handshaker) sendAnswer() error {
IceCredentials: IceCredentials{uFrag, pwd}, IceCredentials: IceCredentials{uFrag, pwd},
WgListenPort: h.config.LocalWgPort, WgListenPort: h.config.LocalWgPort,
Version: version.NetbirdVersion(), Version: version.NetbirdVersion(),
RosenpassPubKey: h.config.RosenpassPubKey, RosenpassPubKey: h.config.RosenpassConfig.PubKey,
RosenpassAddr: h.config.RosenpassAddr, RosenpassAddr: h.config.RosenpassConfig.Addr,
} }
addr, err := h.relay.RelayInstanceAddress() addr, err := h.relay.RelayInstanceAddress()
if err == nil { if err == nil {
+2 -1
View File
@@ -37,7 +37,8 @@ func NewAgent(iFaceDiscover stdnet.ExternalIFaceDiscover, config Config, candida
} }
fac := logging.NewDefaultLoggerFactory() fac := logging.NewDefaultLoggerFactory()
fac.Writer = log.StandardLogger().Writer()
//fac.Writer = log.StandardLogger().Writer()
agentConfig := &ice.AgentConfig{ agentConfig := &ice.AgentConfig{
MulticastDNSMode: ice.MulticastDNSModeDisabled, MulticastDNSMode: ice.MulticastDNSModeDisabled,
+3
View File
@@ -39,6 +39,8 @@ type PKCEAuthProviderConfig struct {
UseIDToken bool UseIDToken bool
// ClientCertPair is used for mTLS authentication to the IDP // ClientCertPair is used for mTLS authentication to the IDP
ClientCertPair *tls.Certificate ClientCertPair *tls.Certificate
// DisablePromptLogin makes the PKCE flow to not prompt the user for login
DisablePromptLogin bool
} }
// GetPKCEAuthorizationFlowInfo initialize a PKCEAuthorizationFlow instance and return with it // GetPKCEAuthorizationFlowInfo initialize a PKCEAuthorizationFlow instance and return with it
@@ -97,6 +99,7 @@ func GetPKCEAuthorizationFlowInfo(ctx context.Context, privateKey string, mgmURL
RedirectURLs: protoPKCEAuthorizationFlow.GetProviderConfig().GetRedirectURLs(), RedirectURLs: protoPKCEAuthorizationFlow.GetProviderConfig().GetRedirectURLs(),
UseIDToken: protoPKCEAuthorizationFlow.GetProviderConfig().GetUseIDToken(), UseIDToken: protoPKCEAuthorizationFlow.GetProviderConfig().GetUseIDToken(),
ClientCertPair: clientCert, ClientCertPair: clientCert,
DisablePromptLogin: protoPKCEAuthorizationFlow.GetProviderConfig().GetDisablePromptLogin(),
}, },
} }
+57 -13
View File
@@ -10,20 +10,27 @@ import (
"golang.org/x/exp/maps" "golang.org/x/exp/maps"
"github.com/netbirdio/netbird/client/errors" "github.com/netbirdio/netbird/client/errors"
route "github.com/netbirdio/netbird/route" "github.com/netbirdio/netbird/route"
) )
type RouteSelector struct { type RouteSelector struct {
mu sync.RWMutex mu sync.RWMutex
selectedRoutes map[route.NetID]struct{} selectedRoutes map[route.NetID]struct{}
selectAll bool selectAll bool
// Indicates if new routes should be automatically selected
includeNewRoutes bool
// All known routes at the time of deselection
knownRoutes []route.NetID
} }
func NewRouteSelector() *RouteSelector { func NewRouteSelector() *RouteSelector {
return &RouteSelector{ return &RouteSelector{
selectedRoutes: map[route.NetID]struct{}{}, selectedRoutes: map[route.NetID]struct{}{},
// default selects all routes selectAll: true,
selectAll: true, includeNewRoutes: false,
knownRoutes: []route.NetID{},
} }
} }
@@ -46,6 +53,7 @@ func (rs *RouteSelector) SelectRoutes(routes []route.NetID, appendRoute bool, al
rs.selectedRoutes[route] = struct{}{} rs.selectedRoutes[route] = struct{}{}
} }
rs.selectAll = false rs.selectAll = false
rs.includeNewRoutes = false
return errors.FormatErrorOrNil(err) return errors.FormatErrorOrNil(err)
} }
@@ -57,16 +65,22 @@ func (rs *RouteSelector) SelectAllRoutes() {
rs.selectAll = true rs.selectAll = true
rs.selectedRoutes = map[route.NetID]struct{}{} rs.selectedRoutes = map[route.NetID]struct{}{}
rs.includeNewRoutes = false
} }
// DeselectRoutes removes specific routes from the selection. // DeselectRoutes removes specific routes from the selection.
// If the selector is in "select all" mode, it will transition to "select specific" mode. // If the selector is in "select all" mode, it will transition to "select specific" mode
// but will keep new routes selected.
func (rs *RouteSelector) DeselectRoutes(routes []route.NetID, allRoutes []route.NetID) error { func (rs *RouteSelector) DeselectRoutes(routes []route.NetID, allRoutes []route.NetID) error {
rs.mu.Lock() rs.mu.Lock()
defer rs.mu.Unlock() defer rs.mu.Unlock()
if rs.selectAll { if rs.selectAll {
rs.selectAll = false rs.selectAll = false
rs.includeNewRoutes = true
rs.knownRoutes = make([]route.NetID, len(allRoutes))
copy(rs.knownRoutes, allRoutes)
rs.selectedRoutes = map[route.NetID]struct{}{} rs.selectedRoutes = map[route.NetID]struct{}{}
for _, route := range allRoutes { for _, route := range allRoutes {
rs.selectedRoutes[route] = struct{}{} rs.selectedRoutes[route] = struct{}{}
@@ -92,6 +106,7 @@ func (rs *RouteSelector) DeselectAllRoutes() {
defer rs.mu.Unlock() defer rs.mu.Unlock()
rs.selectAll = false rs.selectAll = false
rs.includeNewRoutes = false
rs.selectedRoutes = map[route.NetID]struct{}{} rs.selectedRoutes = map[route.NetID]struct{}{}
} }
@@ -103,8 +118,20 @@ func (rs *RouteSelector) IsSelected(routeID route.NetID) bool {
if rs.selectAll { if rs.selectAll {
return true return true
} }
// Check if the route exists in selectedRoutes
_, selected := rs.selectedRoutes[routeID] _, selected := rs.selectedRoutes[routeID]
return selected if selected {
return true
}
// If includeNewRoutes is true and this is a new route (not in knownRoutes),
// then it should be selected
if rs.includeNewRoutes && !slices.Contains(rs.knownRoutes, routeID) {
return true
}
return false
} }
// FilterSelected removes unselected routes from the provided map. // FilterSelected removes unselected routes from the provided map.
@@ -118,7 +145,11 @@ func (rs *RouteSelector) FilterSelected(routes route.HAMap) route.HAMap {
filtered := route.HAMap{} filtered := route.HAMap{}
for id, rt := range routes { for id, rt := range routes {
if rs.IsSelected(id.NetID()) { netID := id.NetID()
_, selected := rs.selectedRoutes[netID]
// Include if directly selected or if it's a new route and includeNewRoutes is true
if selected || (rs.includeNewRoutes && !slices.Contains(rs.knownRoutes, netID)) {
filtered[id] = rt filtered[id] = rt
} }
} }
@@ -131,11 +162,15 @@ func (rs *RouteSelector) MarshalJSON() ([]byte, error) {
defer rs.mu.RUnlock() defer rs.mu.RUnlock()
return json.Marshal(struct { return json.Marshal(struct {
SelectedRoutes map[route.NetID]struct{} `json:"selected_routes"` SelectedRoutes map[route.NetID]struct{} `json:"selected_routes"`
SelectAll bool `json:"select_all"` SelectAll bool `json:"select_all"`
IncludeNewRoutes bool `json:"include_new_routes"`
KnownRoutes []route.NetID `json:"known_routes"`
}{ }{
SelectAll: rs.selectAll, SelectAll: rs.selectAll,
SelectedRoutes: rs.selectedRoutes, SelectedRoutes: rs.selectedRoutes,
IncludeNewRoutes: rs.includeNewRoutes,
KnownRoutes: rs.knownRoutes,
}) })
} }
@@ -149,12 +184,16 @@ func (rs *RouteSelector) UnmarshalJSON(data []byte) error {
if len(data) == 0 || string(data) == "null" { if len(data) == 0 || string(data) == "null" {
rs.selectedRoutes = map[route.NetID]struct{}{} rs.selectedRoutes = map[route.NetID]struct{}{}
rs.selectAll = true rs.selectAll = true
rs.includeNewRoutes = false
rs.knownRoutes = []route.NetID{}
return nil return nil
} }
var temp struct { var temp struct {
SelectedRoutes map[route.NetID]struct{} `json:"selected_routes"` SelectedRoutes map[route.NetID]struct{} `json:"selected_routes"`
SelectAll bool `json:"select_all"` SelectAll bool `json:"select_all"`
IncludeNewRoutes bool `json:"include_new_routes"`
KnownRoutes []route.NetID `json:"known_routes"`
} }
if err := json.Unmarshal(data, &temp); err != nil { if err := json.Unmarshal(data, &temp); err != nil {
@@ -163,10 +202,15 @@ func (rs *RouteSelector) UnmarshalJSON(data []byte) error {
rs.selectedRoutes = temp.SelectedRoutes rs.selectedRoutes = temp.SelectedRoutes
rs.selectAll = temp.SelectAll rs.selectAll = temp.SelectAll
rs.includeNewRoutes = temp.IncludeNewRoutes
rs.knownRoutes = temp.KnownRoutes
if rs.selectedRoutes == nil { if rs.selectedRoutes == nil {
rs.selectedRoutes = map[route.NetID]struct{}{} rs.selectedRoutes = map[route.NetID]struct{}{}
} }
if rs.knownRoutes == nil {
rs.knownRoutes = []route.NetID{}
}
return nil return nil
} }
@@ -316,7 +316,7 @@ func TestRouteSelector_NewRoutesBehavior(t *testing.T) {
return rs.DeselectRoutes([]route.NetID{"route1"}, initialRoutes) return rs.DeselectRoutes([]route.NetID{"route1"}, initialRoutes)
}, },
// After deselecting specific routes, new routes should remain unselected // After deselecting specific routes, new routes should remain unselected
wantNewSelected: []route.NetID{"route2", "route3"}, wantNewSelected: []route.NetID{"route2", "route3", "route4", "route5"},
}, },
{ {
name: "New routes after selecting with append", name: "New routes after selecting with append",
@@ -358,3 +358,73 @@ func TestRouteSelector_NewRoutesBehavior(t *testing.T) {
}) })
} }
} }
func TestRouteSelector_MixedSelectionDeselection(t *testing.T) {
allRoutes := []route.NetID{"route1", "route2", "route3"}
tests := []struct {
name string
routesToSelect []route.NetID
selectAppend bool
routesToDeselect []route.NetID
selectFirst bool
wantSelectedFinal []route.NetID
}{
{
name: "1. Select A, then Deselect B",
routesToSelect: []route.NetID{"route1"},
selectAppend: false,
routesToDeselect: []route.NetID{"route2"},
selectFirst: true,
wantSelectedFinal: []route.NetID{"route1"},
},
{
name: "2. Select A, then Deselect A",
routesToSelect: []route.NetID{"route1"},
selectAppend: false,
routesToDeselect: []route.NetID{"route1"},
selectFirst: true,
wantSelectedFinal: []route.NetID{},
},
{
name: "3. Deselect A (from all), then Select B",
routesToSelect: []route.NetID{"route2"},
selectAppend: false,
routesToDeselect: []route.NetID{"route1"},
selectFirst: false,
wantSelectedFinal: []route.NetID{"route2"},
},
{
name: "4. Deselect A (from all), then Select A",
routesToSelect: []route.NetID{"route1"},
selectAppend: false,
routesToDeselect: []route.NetID{"route1"},
selectFirst: false,
wantSelectedFinal: []route.NetID{"route1"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
rs := routeselector.NewRouteSelector()
var err1, err2 error
if tt.selectFirst {
err1 = rs.SelectRoutes(tt.routesToSelect, tt.selectAppend, allRoutes)
require.NoError(t, err1)
err2 = rs.DeselectRoutes(tt.routesToDeselect, allRoutes)
require.NoError(t, err2)
} else {
err1 = rs.DeselectRoutes(tt.routesToDeselect, allRoutes)
require.NoError(t, err1)
err2 = rs.SelectRoutes(tt.routesToSelect, tt.selectAppend, allRoutes)
require.NoError(t, err2)
}
for _, r := range allRoutes {
assert.Equal(t, slices.Contains(tt.wantSelectedFinal, r), rs.IsSelected(r), "Route %s final state mismatch", r)
}
})
}
}
+24 -963
View File
File diff suppressed because it is too large Load Diff
-15
View File
@@ -1,15 +0,0 @@
//go:build !linux || android
package server
import (
"archive/zip"
"github.com/netbirdio/netbird/client/anonymize"
"github.com/netbirdio/netbird/client/proto"
)
// collectFirewallRules returns nothing on non-linux systems
func (s *Server) addFirewallRules(req *proto.DebugBundleRequest, anonymizer *anonymize.Anonymizer, archive *zip.Writer) error {
return nil
}
+3 -2
View File
@@ -8,10 +8,11 @@ import (
"time" "time"
"github.com/golang/mock/gomock" "github.com/golang/mock/gomock"
"github.com/netbirdio/management-integrations/integrations"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"go.opentelemetry.io/otel" "go.opentelemetry.io/otel"
"github.com/netbirdio/management-integrations/integrations"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"google.golang.org/grpc" "google.golang.org/grpc"
@@ -200,10 +201,10 @@ func startManagement(t *testing.T, signalAddr string, counter *int) (*grpc.Serve
metrics, err := telemetry.NewDefaultAppMetrics(context.Background()) metrics, err := telemetry.NewDefaultAppMetrics(context.Background())
require.NoError(t, err) require.NoError(t, err)
permissionsManagerMock := permissions.NewManagerMock()
ctrl := gomock.NewController(t) ctrl := gomock.NewController(t)
t.Cleanup(ctrl.Finish) t.Cleanup(ctrl.Finish)
settingsMockManager := settings.NewMockManager(ctrl) settingsMockManager := settings.NewMockManager(ctrl)
permissionsManagerMock := permissions.NewMockManager(ctrl)
accountManager, err := server.BuildManager(context.Background(), store, peersUpdateManager, nil, "", "netbird.selfhosted", eventStore, nil, false, ia, metrics, port_forwarding.NewControllerMock(), settingsMockManager, permissionsManagerMock) accountManager, err := server.BuildManager(context.Background(), store, peersUpdateManager, nil, "", "netbird.selfhosted", eventStore, nil, false, ia, metrics, port_forwarding.NewControllerMock(), settingsMockManager, permissionsManagerMock)
if err != nil { if err != nil {
+66 -62
View File
@@ -569,7 +569,7 @@ var file_flow_flow_proto_rawDesc = []byte{
0x0a, 0x0f, 0x66, 0x6c, 0x6f, 0x77, 0x2f, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x0a, 0x0f, 0x66, 0x6c, 0x6f, 0x77, 0x2f, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x12, 0x04, 0x66, 0x6c, 0x6f, 0x77, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x6f, 0x12, 0x04, 0x66, 0x6c, 0x6f, 0x77, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61,
0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xb2, 0x01, 0x0a, 0x09, 0x46, 0x6c, 0x6f, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xd4, 0x01, 0x0a, 0x09, 0x46, 0x6c, 0x6f,
0x77, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x5f, 0x77, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x5f,
0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x49, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x49,
0x64, 0x12, 0x38, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x02, 0x64, 0x12, 0x38, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x02,
@@ -580,67 +580,71 @@ var file_flow_flow_proto_rawDesc = []byte{
0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x31, 0x0a, 0x0b, 0x66, 0x6c, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x31, 0x0a, 0x0b, 0x66, 0x6c,
0x6f, 0x77, 0x5f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x6f, 0x77, 0x5f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32,
0x10, 0x2e, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x46, 0x6c, 0x6f, 0x77, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x10, 0x2e, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x46, 0x6c, 0x6f, 0x77, 0x46, 0x69, 0x65, 0x6c, 0x64,
0x73, 0x52, 0x0a, 0x66, 0x6c, 0x6f, 0x77, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x22, 0x29, 0x0a, 0x73, 0x52, 0x0a, 0x66, 0x6c, 0x6f, 0x77, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x12, 0x20, 0x0a,
0x0c, 0x46, 0x6c, 0x6f, 0x77, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x41, 0x63, 0x6b, 0x12, 0x19, 0x0a, 0x0b, 0x69, 0x73, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x6f, 0x72, 0x18, 0x05, 0x20, 0x01,
0x08, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x28, 0x08, 0x52, 0x0b, 0x69, 0x73, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x6f, 0x72, 0x22,
0x07, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x22, 0x9c, 0x04, 0x0a, 0x0a, 0x46, 0x6c, 0x6f, 0x4b, 0x0a, 0x0c, 0x46, 0x6c, 0x6f, 0x77, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x41, 0x63, 0x6b, 0x12,
0x77, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x12, 0x17, 0x0a, 0x07, 0x66, 0x6c, 0x6f, 0x77, 0x5f, 0x19, 0x0a, 0x08, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,
0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x66, 0x6c, 0x6f, 0x77, 0x49, 0x64, 0x0c, 0x52, 0x07, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x20, 0x0a, 0x0b, 0x69, 0x73,
0x12, 0x1e, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0a, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52,
0x2e, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x0b, 0x69, 0x73, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x6f, 0x72, 0x22, 0x9c, 0x04, 0x0a,
0x12, 0x17, 0x0a, 0x07, 0x72, 0x75, 0x6c, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0a, 0x46, 0x6c, 0x6f, 0x77, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x12, 0x17, 0x0a, 0x07, 0x66,
0x0c, 0x52, 0x06, 0x72, 0x75, 0x6c, 0x65, 0x49, 0x64, 0x12, 0x2d, 0x0a, 0x09, 0x64, 0x69, 0x72, 0x6c, 0x6f, 0x77, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x66, 0x6c,
0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0f, 0x2e, 0x66, 0x6f, 0x77, 0x49, 0x64, 0x12, 0x1e, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01,
0x6c, 0x6f, 0x77, 0x2e, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x09, 0x64, 0x28, 0x0e, 0x32, 0x0a, 0x2e, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04,
0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x74, 0x79, 0x70, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x75, 0x6c, 0x65, 0x5f, 0x69, 0x64, 0x18,
0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x72, 0x75, 0x6c, 0x65, 0x49, 0x64, 0x12, 0x2d, 0x0a,
0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x1b, 0x0a, 0x09, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e,
0x70, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x32, 0x0f, 0x2e, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f,
0x70, 0x12, 0x17, 0x0a, 0x07, 0x64, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x70, 0x18, 0x07, 0x20, 0x01, 0x6e, 0x52, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08,
0x28, 0x0c, 0x52, 0x06, 0x64, 0x65, 0x73, 0x74, 0x49, 0x70, 0x12, 0x2d, 0x0a, 0x09, 0x70, 0x6f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08,
0x72, 0x74, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x1b, 0x0a, 0x09, 0x73, 0x6f, 0x75, 0x72,
0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x50, 0x6f, 0x72, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x48, 0x00, 0x52, 0x63, 0x65, 0x5f, 0x69, 0x70, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x73, 0x6f, 0x75,
0x08, 0x70, 0x6f, 0x72, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x2d, 0x0a, 0x09, 0x69, 0x63, 0x6d, 0x72, 0x63, 0x65, 0x49, 0x70, 0x12, 0x17, 0x0a, 0x07, 0x64, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x70,
0x70, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x66, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x64, 0x65, 0x73, 0x74, 0x49, 0x70, 0x12, 0x2d,
0x6c, 0x6f, 0x77, 0x2e, 0x49, 0x43, 0x4d, 0x50, 0x49, 0x6e, 0x66, 0x6f, 0x48, 0x00, 0x52, 0x08, 0x0a, 0x09, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x08, 0x20, 0x01, 0x28,
0x69, 0x63, 0x6d, 0x70, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x78, 0x5f, 0x70, 0x0b, 0x32, 0x0e, 0x2e, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x50, 0x6f, 0x72, 0x74, 0x49, 0x6e, 0x66,
0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x72, 0x78, 0x6f, 0x48, 0x00, 0x52, 0x08, 0x70, 0x6f, 0x72, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x2d, 0x0a,
0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x74, 0x78, 0x5f, 0x70, 0x61, 0x09, 0x69, 0x63, 0x6d, 0x70, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b,
0x63, 0x6b, 0x65, 0x74, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x74, 0x78, 0x50, 0x32, 0x0e, 0x2e, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x49, 0x43, 0x4d, 0x50, 0x49, 0x6e, 0x66, 0x6f,
0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x72, 0x78, 0x5f, 0x62, 0x79, 0x74, 0x48, 0x00, 0x52, 0x08, 0x69, 0x63, 0x6d, 0x70, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1d, 0x0a, 0x0a,
0x65, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x72, 0x78, 0x42, 0x79, 0x74, 0x65, 0x72, 0x78, 0x5f, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x04,
0x73, 0x12, 0x19, 0x0a, 0x08, 0x74, 0x78, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x0d, 0x20, 0x52, 0x09, 0x72, 0x78, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x74,
0x01, 0x28, 0x04, 0x52, 0x07, 0x74, 0x78, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x78, 0x5f, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x04, 0x52,
0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x09, 0x74, 0x78, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x72, 0x78,
0x69, 0x64, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x10, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x72, 0x78,
0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x64, 0x65, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x74, 0x78, 0x5f, 0x62, 0x79, 0x74, 0x65,
0x73, 0x74, 0x5f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x0f, 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x74, 0x78, 0x42, 0x79, 0x74, 0x65, 0x73,
0x20, 0x01, 0x28, 0x0c, 0x52, 0x0e, 0x64, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x12, 0x2c, 0x0a, 0x12, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x72, 0x65, 0x73, 0x6f, 0x75,
0x63, 0x65, 0x49, 0x64, 0x42, 0x11, 0x0a, 0x0f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x10, 0x73, 0x6f,
0x6f, 0x6e, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x22, 0x48, 0x0a, 0x08, 0x50, 0x6f, 0x72, 0x74, 0x49, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, 0x12, 0x28,
0x6e, 0x66, 0x6f, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x70, 0x6f, 0x0a, 0x10, 0x64, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f,
0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x69, 0x64, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0e, 0x64, 0x65, 0x73, 0x74, 0x52, 0x65,
0x50, 0x6f, 0x72, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x64, 0x65, 0x73, 0x74, 0x5f, 0x70, 0x6f, 0x72, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, 0x42, 0x11, 0x0a, 0x0f, 0x63, 0x6f, 0x6e, 0x6e,
0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x64, 0x65, 0x73, 0x74, 0x50, 0x6f, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x22, 0x48, 0x0a, 0x08, 0x50,
0x74, 0x22, 0x44, 0x0a, 0x08, 0x49, 0x43, 0x4d, 0x50, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1b, 0x0a, 0x6f, 0x72, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x6f, 0x75, 0x72, 0x63,
0x09, 0x69, 0x63, 0x6d, 0x70, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x65, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x73, 0x6f,
0x52, 0x08, 0x69, 0x63, 0x6d, 0x70, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x69, 0x63, 0x75, 0x72, 0x63, 0x65, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x64, 0x65, 0x73, 0x74,
0x6d, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x69, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x64, 0x65, 0x73,
0x63, 0x6d, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x2a, 0x45, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x74, 0x50, 0x6f, 0x72, 0x74, 0x22, 0x44, 0x0a, 0x08, 0x49, 0x43, 0x4d, 0x50, 0x49, 0x6e, 0x66,
0x10, 0x0a, 0x0c, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x6f, 0x12, 0x1b, 0x0a, 0x09, 0x69, 0x63, 0x6d, 0x70, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01,
0x00, 0x12, 0x0e, 0x0a, 0x0a, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, 0x54, 0x41, 0x52, 0x54, 0x10, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x69, 0x63, 0x6d, 0x70, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1b,
0x01, 0x12, 0x0c, 0x0a, 0x08, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x45, 0x4e, 0x44, 0x10, 0x02, 0x12, 0x0a, 0x09, 0x69, 0x63, 0x6d, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
0x0d, 0x0a, 0x09, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x44, 0x52, 0x4f, 0x50, 0x10, 0x03, 0x2a, 0x3b, 0x0d, 0x52, 0x08, 0x69, 0x63, 0x6d, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x2a, 0x45, 0x0a, 0x04, 0x54,
0x0a, 0x09, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x15, 0x0a, 0x11, 0x44, 0x79, 0x70, 0x65, 0x12, 0x10, 0x0a, 0x0c, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e,
0x49, 0x52, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0e, 0x0a, 0x0a, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, 0x54,
0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x49, 0x4e, 0x47, 0x52, 0x45, 0x53, 0x53, 0x10, 0x01, 0x12, 0x41, 0x52, 0x54, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x45, 0x4e,
0x0a, 0x0a, 0x06, 0x45, 0x47, 0x52, 0x45, 0x53, 0x53, 0x10, 0x02, 0x32, 0x42, 0x0a, 0x0b, 0x46, 0x44, 0x10, 0x02, 0x12, 0x0d, 0x0a, 0x09, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x44, 0x52, 0x4f, 0x50,
0x6c, 0x6f, 0x77, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x33, 0x0a, 0x06, 0x45, 0x76, 0x10, 0x03, 0x2a, 0x3b, 0x0a, 0x09, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12,
0x65, 0x6e, 0x74, 0x73, 0x12, 0x0f, 0x2e, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x46, 0x6c, 0x6f, 0x77, 0x15, 0x0a, 0x11, 0x44, 0x49, 0x52, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x55, 0x4e, 0x4b,
0x45, 0x76, 0x65, 0x6e, 0x74, 0x1a, 0x12, 0x2e, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x46, 0x6c, 0x6f, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x49, 0x4e, 0x47, 0x52, 0x45, 0x53,
0x77, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x41, 0x63, 0x6b, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x42, 0x53, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x45, 0x47, 0x52, 0x45, 0x53, 0x53, 0x10, 0x02, 0x32,
0x0c, 0x5a, 0x0a, 0x66, 0x6c, 0x6f, 0x77, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x42, 0x0a, 0x0b, 0x46, 0x6c, 0x6f, 0x77, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x33,
0x72, 0x6f, 0x74, 0x6f, 0x33, 0x0a, 0x06, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x0f, 0x2e, 0x66, 0x6c, 0x6f, 0x77, 0x2e,
0x46, 0x6c, 0x6f, 0x77, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x1a, 0x12, 0x2e, 0x66, 0x6c, 0x6f, 0x77,
0x2e, 0x46, 0x6c, 0x6f, 0x77, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x41, 0x63, 0x6b, 0x22, 0x00, 0x28,
0x01, 0x30, 0x01, 0x42, 0x0c, 0x5a, 0x0a, 0x66, 0x6c, 0x6f, 0x77, 0x2f, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
} }
var ( var (
+2
View File
@@ -58,6 +58,7 @@ NETBIRD_TOKEN_SOURCE=${NETBIRD_TOKEN_SOURCE:-accessToken}
# PKCE authorization flow # PKCE authorization flow
NETBIRD_AUTH_PKCE_REDIRECT_URL_PORTS=${NETBIRD_AUTH_PKCE_REDIRECT_URL_PORTS:-"53000"} NETBIRD_AUTH_PKCE_REDIRECT_URL_PORTS=${NETBIRD_AUTH_PKCE_REDIRECT_URL_PORTS:-"53000"}
NETBIRD_AUTH_PKCE_USE_ID_TOKEN=${NETBIRD_AUTH_PKCE_USE_ID_TOKEN:-false} NETBIRD_AUTH_PKCE_USE_ID_TOKEN=${NETBIRD_AUTH_PKCE_USE_ID_TOKEN:-false}
NETBIRD_AUTH_PKCE_DISABLE_PROMPT_LOGIN=${NETBIRD_AUTH_PKCE_DISABLE_PROMPT_LOGIN:-false}
NETBIRD_AUTH_PKCE_AUDIENCE=$NETBIRD_AUTH_AUDIENCE NETBIRD_AUTH_PKCE_AUDIENCE=$NETBIRD_AUTH_AUDIENCE
# Dashboard # Dashboard
@@ -120,6 +121,7 @@ export NETBIRD_AUTH_DEVICE_AUTH_SCOPE
export NETBIRD_AUTH_DEVICE_AUTH_USE_ID_TOKEN export NETBIRD_AUTH_DEVICE_AUTH_USE_ID_TOKEN
export NETBIRD_AUTH_PKCE_AUTHORIZATION_ENDPOINT export NETBIRD_AUTH_PKCE_AUTHORIZATION_ENDPOINT
export NETBIRD_AUTH_PKCE_USE_ID_TOKEN export NETBIRD_AUTH_PKCE_USE_ID_TOKEN
export NETBIRD_AUTH_PKCE_DISABLE_PROMPT_LOGIN
export NETBIRD_AUTH_PKCE_AUDIENCE export NETBIRD_AUTH_PKCE_AUDIENCE
export NETBIRD_DASH_AUTH_USE_AUDIENCE export NETBIRD_DASH_AUTH_USE_AUDIENCE
export NETBIRD_DASH_AUTH_AUDIENCE export NETBIRD_DASH_AUTH_AUDIENCE
+2 -1
View File
@@ -94,7 +94,8 @@
"TokenEndpoint": "$NETBIRD_AUTH_TOKEN_ENDPOINT", "TokenEndpoint": "$NETBIRD_AUTH_TOKEN_ENDPOINT",
"Scope": "$NETBIRD_AUTH_SUPPORTED_SCOPES", "Scope": "$NETBIRD_AUTH_SUPPORTED_SCOPES",
"RedirectURLs": [$NETBIRD_AUTH_PKCE_REDIRECT_URLS], "RedirectURLs": [$NETBIRD_AUTH_PKCE_REDIRECT_URLS],
"UseIDToken": $NETBIRD_AUTH_PKCE_USE_ID_TOKEN "UseIDToken": $NETBIRD_AUTH_PKCE_USE_ID_TOKEN,
"DisablePromptLogin": $NETBIRD_AUTH_PKCE_DISABLE_PROMPT_LOGIN
} }
} }
} }
+1
View File
@@ -27,3 +27,4 @@ NETBIRD_STORE_CONFIG_ENGINE=$CI_NETBIRD_STORE_CONFIG_ENGINE
NETBIRD_MGMT_IDP_SIGNKEY_REFRESH=$CI_NETBIRD_MGMT_IDP_SIGNKEY_REFRESH NETBIRD_MGMT_IDP_SIGNKEY_REFRESH=$CI_NETBIRD_MGMT_IDP_SIGNKEY_REFRESH
NETBIRD_TURN_EXTERNAL_IP=1.2.3.4 NETBIRD_TURN_EXTERNAL_IP=1.2.3.4
NETBIRD_RELAY_PORT=33445 NETBIRD_RELAY_PORT=33445
NETBIRD_AUTH_PKCE_DISABLE_PROMPT_LOGIN=true
+12 -1
View File
@@ -75,7 +75,6 @@ func startManagement(t *testing.T) (*grpc.Server, net.Listener) {
metrics, err := telemetry.NewDefaultAppMetrics(context.Background()) metrics, err := telemetry.NewDefaultAppMetrics(context.Background())
require.NoError(t, err) require.NoError(t, err)
permissionsManagerMock := permissions.NewManagerMock()
ctrl := gomock.NewController(t) ctrl := gomock.NewController(t)
t.Cleanup(ctrl.Finish) t.Cleanup(ctrl.Finish)
settingsMockManager := settings.NewMockManager(ctrl) settingsMockManager := settings.NewMockManager(ctrl)
@@ -88,6 +87,18 @@ func startManagement(t *testing.T) (*grpc.Server, net.Listener) {
). ).
Return(&types.Settings{}, nil). Return(&types.Settings{}, nil).
AnyTimes() AnyTimes()
permissionsManagerMock := permissions.NewMockManager(ctrl)
permissionsManagerMock.
EXPECT().
ValidateUserPermissions(
gomock.Any(),
gomock.Any(),
gomock.Any(),
gomock.Any(),
gomock.Any(),
).
Return(true, nil).
AnyTimes()
accountManager, err := mgmt.BuildManager(context.Background(), store, peersUpdateManager, nil, "", "netbird.selfhosted", eventStore, nil, false, ia, metrics, port_forwarding.NewControllerMock(), settingsMockManager, permissionsManagerMock) accountManager, err := mgmt.BuildManager(context.Background(), store, peersUpdateManager, nil, "", "netbird.selfhosted", eventStore, nil, false, ia, metrics, port_forwarding.NewControllerMock(), settingsMockManager, permissionsManagerMock)
if err != nil { if err != nil {
+13
View File
@@ -80,3 +80,16 @@ func (a *UsersAPI) ResendInvitation(ctx context.Context, userID string) error {
return nil return nil
} }
// Current gets the current user info
// See more: https://docs.netbird.io/api/resources/users#retrieve-current-user
func (a *UsersAPI) Current(ctx context.Context) (*api.User, error) {
resp, err := a.c.newRequest(ctx, "GET", "/api/users/current", nil)
if err != nil {
return nil, err
}
defer resp.Body.Close()
ret, err := parseResponse[api.User](resp)
return &ret, err
}
+34
View File
@@ -196,8 +196,42 @@ func TestUsers_ResendInvitation_Err(t *testing.T) {
}) })
} }
func TestUsers_Current_200(t *testing.T) {
withMockClient(func(c *rest.Client, mux *http.ServeMux) {
mux.HandleFunc("/api/users/current", func(w http.ResponseWriter, r *http.Request) {
retBytes, _ := json.Marshal(testUser)
_, err := w.Write(retBytes)
require.NoError(t, err)
})
ret, err := c.Users.Current(context.Background())
require.NoError(t, err)
assert.Equal(t, testUser, *ret)
})
}
func TestUsers_Current_Err(t *testing.T) {
withMockClient(func(c *rest.Client, mux *http.ServeMux) {
mux.HandleFunc("/api/users/current", func(w http.ResponseWriter, r *http.Request) {
retBytes, _ := json.Marshal(util.ErrorResponse{Message: "No", Code: 400})
w.WriteHeader(400)
_, err := w.Write(retBytes)
require.NoError(t, err)
})
ret, err := c.Users.Current(context.Background())
assert.Error(t, err)
assert.Equal(t, "No", err.Error())
assert.Empty(t, ret)
})
}
func TestUsers_Integration(t *testing.T) { func TestUsers_Integration(t *testing.T) {
withBlackBoxServer(t, func(c *rest.Client) { withBlackBoxServer(t, func(c *rest.Client) {
// rest client PAT is owner's
current, err := c.Users.Current(context.Background())
require.NoError(t, err)
assert.Equal(t, "a23efe53-63fb-11ec-90d6-0242ac120003", current.Id)
assert.Equal(t, "owner", current.Role)
user, err := c.Users.Create(context.Background(), api.UserCreateRequest{ user, err := c.Users.Create(context.Background(), api.UserCreateRequest{
AutoGroups: []string{}, AutoGroups: []string{},
Email: ptr("test@example.com"), Email: ptr("test@example.com"),
+13 -1
View File
@@ -2140,6 +2140,8 @@ type ProviderConfig struct {
AuthorizationEndpoint string `protobuf:"bytes,9,opt,name=AuthorizationEndpoint,proto3" json:"AuthorizationEndpoint,omitempty"` AuthorizationEndpoint string `protobuf:"bytes,9,opt,name=AuthorizationEndpoint,proto3" json:"AuthorizationEndpoint,omitempty"`
// RedirectURLs handles authorization code from IDP manager // RedirectURLs handles authorization code from IDP manager
RedirectURLs []string `protobuf:"bytes,10,rep,name=RedirectURLs,proto3" json:"RedirectURLs,omitempty"` RedirectURLs []string `protobuf:"bytes,10,rep,name=RedirectURLs,proto3" json:"RedirectURLs,omitempty"`
// DisablePromptLogin makes the PKCE flow to not prompt the user for login
DisablePromptLogin bool `protobuf:"varint,11,opt,name=DisablePromptLogin,proto3" json:"DisablePromptLogin,omitempty"`
} }
func (x *ProviderConfig) Reset() { func (x *ProviderConfig) Reset() {
@@ -2242,6 +2244,13 @@ func (x *ProviderConfig) GetRedirectURLs() []string {
return nil return nil
} }
func (x *ProviderConfig) GetDisablePromptLogin() bool {
if x != nil {
return x.DisablePromptLogin
}
return false
}
// Route represents a route.Route object // Route represents a route.Route object
type Route struct { type Route struct {
state protoimpl.MessageState state protoimpl.MessageState
@@ -3499,7 +3508,7 @@ var file_management_management_proto_rawDesc = []byte{
0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65,
0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e,
0x66, 0x69, 0x67, 0x52, 0x0e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e,
0x66, 0x69, 0x67, 0x22, 0xea, 0x02, 0x0a, 0x0e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x66, 0x69, 0x67, 0x22, 0x9a, 0x03, 0x0a, 0x0e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72,
0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1a, 0x0a, 0x08, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1a, 0x0a, 0x08, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74,
0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74,
0x49, 0x44, 0x12, 0x22, 0x0a, 0x0c, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x63, 0x72, 0x49, 0x44, 0x12, 0x22, 0x0a, 0x0c, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x63, 0x72,
@@ -3522,6 +3531,9 @@ var file_management_management_proto_rawDesc = []byte{
0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x22, 0x0a, 0x0c, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x22, 0x0a, 0x0c,
0x52, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x55, 0x52, 0x4c, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x52, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x55, 0x52, 0x4c, 0x73, 0x18, 0x0a, 0x20, 0x03,
0x28, 0x09, 0x52, 0x0c, 0x52, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x55, 0x52, 0x4c, 0x73, 0x28, 0x09, 0x52, 0x0c, 0x52, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x55, 0x52, 0x4c, 0x73,
0x12, 0x2e, 0x0a, 0x12, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x50, 0x72, 0x6f, 0x6d, 0x70,
0x74, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x44, 0x69,
0x73, 0x61, 0x62, 0x6c, 0x65, 0x50, 0x72, 0x6f, 0x6d, 0x70, 0x74, 0x4c, 0x6f, 0x67, 0x69, 0x6e,
0x22, 0xed, 0x01, 0x0a, 0x05, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x44, 0x22, 0xed, 0x01, 0x0a, 0x05, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x44,
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x44, 0x12, 0x18, 0x0a, 0x07, 0x4e, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x44, 0x12, 0x18, 0x0a, 0x07, 0x4e, 0x65,
0x74, 0x77, 0x6f, 0x72, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x4e, 0x65, 0x74, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x4e, 0x65, 0x74,
+37 -19
View File
@@ -6,11 +6,14 @@ import (
"fmt" "fmt"
"math/rand" "math/rand"
"net" "net"
"os"
"reflect" "reflect"
"regexp" "regexp"
"slices" "slices"
"strconv"
"strings" "strings"
"sync" "sync"
"sync/atomic"
"time" "time"
cacheStore "github.com/eko/gocache/lib/v4/store" cacheStore "github.com/eko/gocache/lib/v4/store"
@@ -30,6 +33,8 @@ import (
"github.com/netbirdio/netbird/management/server/integrations/port_forwarding" "github.com/netbirdio/netbird/management/server/integrations/port_forwarding"
nbpeer "github.com/netbirdio/netbird/management/server/peer" nbpeer "github.com/netbirdio/netbird/management/server/peer"
"github.com/netbirdio/netbird/management/server/permissions" "github.com/netbirdio/netbird/management/server/permissions"
"github.com/netbirdio/netbird/management/server/permissions/modules"
"github.com/netbirdio/netbird/management/server/permissions/operations"
"github.com/netbirdio/netbird/management/server/posture" "github.com/netbirdio/netbird/management/server/posture"
"github.com/netbirdio/netbird/management/server/settings" "github.com/netbirdio/netbird/management/server/settings"
"github.com/netbirdio/netbird/management/server/status" "github.com/netbirdio/netbird/management/server/status"
@@ -92,6 +97,9 @@ type DefaultAccountManager struct {
metrics telemetry.AppMetrics metrics telemetry.AppMetrics
permissionsManager permissions.Manager permissionsManager permissions.Manager
accountUpdateLocks sync.Map
updateAccountPeersBufferInterval atomic.Int64
} }
// getJWTGroupsChanges calculates the changes needed to sync a user's JWT groups. // getJWTGroupsChanges calculates the changes needed to sync a user's JWT groups.
@@ -186,6 +194,23 @@ func BuildManager(
settingsManager: settingsManager, settingsManager: settingsManager,
permissionsManager: permissionsManager, permissionsManager: permissionsManager,
} }
var initialInterval int64
intervalStr := os.Getenv("PEER_UPDATE_INTERVAL_MS")
interval, err := strconv.Atoi(intervalStr)
if err != nil {
initialInterval = 1
} else {
initialInterval = int64(interval) * 10
go func() {
time.Sleep(30 * time.Second)
am.updateAccountPeersBufferInterval.Store(int64(time.Duration(interval) * time.Millisecond))
log.WithContext(ctx).Infof("set peer update buffer interval to %dms", interval)
}()
}
am.updateAccountPeersBufferInterval.Store(initialInterval)
log.WithContext(ctx).Infof("set peer update buffer interval to %dms", initialInterval)
accountsCounter, err := store.GetAccountsCounter(ctx) accountsCounter, err := store.GetAccountsCounter(ctx)
if err != nil { if err != nil {
log.WithContext(ctx).Error(err) log.WithContext(ctx).Error(err)
@@ -258,7 +283,7 @@ func (am *DefaultAccountManager) UpdateAccountSettings(ctx context.Context, acco
return nil, err return nil, err
} }
allowed, err := am.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, permissions.Settings, permissions.Write) allowed, err := am.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Settings, operations.Update)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to validate user permissions: %w", err) return nil, fmt.Errorf("failed to validate user permissions: %w", err)
} }
@@ -508,7 +533,7 @@ func (am *DefaultAccountManager) DeleteAccount(ctx context.Context, accountID, u
return err return err
} }
allowed, err := am.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, permissions.Accounts, permissions.Write) allowed, err := am.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Accounts, operations.Delete)
if err != nil { if err != nil {
return fmt.Errorf("failed to validate user permissions: %w", err) return fmt.Errorf("failed to validate user permissions: %w", err)
} }
@@ -1021,13 +1046,12 @@ func (am *DefaultAccountManager) GetAccount(ctx context.Context, accountID strin
// GetAccountByID returns an account associated with this account ID. // GetAccountByID returns an account associated with this account ID.
func (am *DefaultAccountManager) GetAccountByID(ctx context.Context, accountID string, userID string) (*types.Account, error) { func (am *DefaultAccountManager) GetAccountByID(ctx context.Context, accountID string, userID string) (*types.Account, error) {
user, err := am.Store.GetUserByUserID(ctx, store.LockingStrengthShare, userID) allowed, err := am.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Accounts, operations.Read)
if err != nil { if err != nil {
return nil, err return nil, status.NewPermissionValidationError(err)
} }
if !allowed {
if err := am.permissionsManager.ValidateAccountAccess(ctx, accountID, user, false); err != nil { return nil, status.NewPermissionDeniedError()
return nil, err
} }
return am.Store.GetAccount(ctx, accountID) return am.Store.GetAccount(ctx, accountID)
@@ -1223,7 +1247,7 @@ func (am *DefaultAccountManager) SyncUserJWTGroups(ctx context.Context, userAuth
if removedGroupAffectsPeers || newGroupsAffectsPeers { if removedGroupAffectsPeers || newGroupsAffectsPeers {
log.WithContext(ctx).Tracef("user %s: JWT group membership changed, updating account peers", userAuth.UserId) log.WithContext(ctx).Tracef("user %s: JWT group membership changed, updating account peers", userAuth.UserId)
am.UpdateAccountPeers(ctx, userAuth.AccountId) am.BufferUpdateAccountPeers(ctx, userAuth.AccountId)
} }
} }
@@ -1462,7 +1486,7 @@ func (am *DefaultAccountManager) GetDNSDomain() string {
func (am *DefaultAccountManager) onPeersInvalidated(ctx context.Context, accountID string) { func (am *DefaultAccountManager) onPeersInvalidated(ctx context.Context, accountID string) {
log.WithContext(ctx).Debugf("validated peers has been invalidated for account %s", accountID) log.WithContext(ctx).Debugf("validated peers has been invalidated for account %s", accountID)
am.UpdateAccountPeers(ctx, accountID) am.BufferUpdateAccountPeers(ctx, accountID)
} }
func (am *DefaultAccountManager) FindExistingPostureCheck(accountID string, checks *posture.ChecksDefinition) (*posture.Checks, error) { func (am *DefaultAccountManager) FindExistingPostureCheck(accountID string, checks *posture.ChecksDefinition) (*posture.Checks, error) {
@@ -1515,19 +1539,13 @@ func (am *DefaultAccountManager) getFreeDNSLabel(ctx context.Context, s store.St
} }
func (am *DefaultAccountManager) GetAccountSettings(ctx context.Context, accountID string, userID string) (*types.Settings, error) { func (am *DefaultAccountManager) GetAccountSettings(ctx context.Context, accountID string, userID string) (*types.Settings, error) {
user, err := am.Store.GetUserByUserID(ctx, store.LockingStrengthShare, userID) allowed, err := am.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Settings, operations.Read)
if err != nil { if err != nil {
return nil, err return nil, status.NewPermissionValidationError(err)
} }
if !allowed {
if err := am.permissionsManager.ValidateAccountAccess(ctx, accountID, user, false); err != nil { return nil, status.NewPermissionDeniedError()
return nil, err
} }
if !user.HasAdminPower() && !user.IsServiceUser {
return nil, status.Errorf(status.PermissionDenied, "the user has no permission to access account data")
}
return am.Store.GetAccountSettings(ctx, store.LockingStrengthShare, accountID) return am.Store.GetAccountSettings(ctx, store.LockingStrengthShare, accountID)
} }
+5 -4
View File
@@ -59,15 +59,15 @@ type Manager interface {
GetGroup(ctx context.Context, accountId, groupID, userID string) (*types.Group, error) GetGroup(ctx context.Context, accountId, groupID, userID string) (*types.Group, error)
GetAllGroups(ctx context.Context, accountID, userID string) ([]*types.Group, error) GetAllGroups(ctx context.Context, accountID, userID string) ([]*types.Group, error)
GetGroupByName(ctx context.Context, groupName, accountID string) (*types.Group, error) GetGroupByName(ctx context.Context, groupName, accountID string) (*types.Group, error)
SaveGroup(ctx context.Context, accountID, userID string, group *types.Group) error SaveGroup(ctx context.Context, accountID, userID string, group *types.Group, create bool) error
SaveGroups(ctx context.Context, accountID, userID string, newGroups []*types.Group) error SaveGroups(ctx context.Context, accountID, userID string, newGroups []*types.Group, create bool) error
DeleteGroup(ctx context.Context, accountId, userId, groupID string) error DeleteGroup(ctx context.Context, accountId, userId, groupID string) error
DeleteGroups(ctx context.Context, accountId, userId string, groupIDs []string) error DeleteGroups(ctx context.Context, accountId, userId string, groupIDs []string) error
GroupAddPeer(ctx context.Context, accountId, groupID, peerID string) error GroupAddPeer(ctx context.Context, accountId, groupID, peerID string) error
GroupDeletePeer(ctx context.Context, accountId, groupID, peerID string) error GroupDeletePeer(ctx context.Context, accountId, groupID, peerID string) error
GetPeerGroups(ctx context.Context, accountID, peerID string) ([]*types.Group, error) GetPeerGroups(ctx context.Context, accountID, peerID string) ([]*types.Group, error)
GetPolicy(ctx context.Context, accountID, policyID, userID string) (*types.Policy, error) GetPolicy(ctx context.Context, accountID, policyID, userID string) (*types.Policy, error)
SavePolicy(ctx context.Context, accountID, userID string, policy *types.Policy) (*types.Policy, error) SavePolicy(ctx context.Context, accountID, userID string, policy *types.Policy, create bool) (*types.Policy, error)
DeletePolicy(ctx context.Context, accountID, policyID, userID string) error DeletePolicy(ctx context.Context, accountID, policyID, userID string) error
ListPolicies(ctx context.Context, accountID, userID string) ([]*types.Policy, error) ListPolicies(ctx context.Context, accountID, userID string) ([]*types.Policy, error)
GetRoute(ctx context.Context, accountID string, routeID route.ID, userID string) (*route.Route, error) GetRoute(ctx context.Context, accountID string, routeID route.ID, userID string) (*route.Route, error)
@@ -93,7 +93,7 @@ type Manager interface {
HasConnectedChannel(peerID string) bool HasConnectedChannel(peerID string) bool
GetExternalCacheManager() ExternalCacheManager GetExternalCacheManager() ExternalCacheManager
GetPostureChecks(ctx context.Context, accountID, postureChecksID, userID string) (*posture.Checks, error) GetPostureChecks(ctx context.Context, accountID, postureChecksID, userID string) (*posture.Checks, error)
SavePostureChecks(ctx context.Context, accountID, userID string, postureChecks *posture.Checks) (*posture.Checks, error) SavePostureChecks(ctx context.Context, accountID, userID string, postureChecks *posture.Checks, create bool) (*posture.Checks, error)
DeletePostureChecks(ctx context.Context, accountID, postureChecksID, userID string) error DeletePostureChecks(ctx context.Context, accountID, postureChecksID, userID string) error
ListPostureChecks(ctx context.Context, accountID, userID string) ([]*posture.Checks, error) ListPostureChecks(ctx context.Context, accountID, userID string) ([]*posture.Checks, error)
GetIdpManager() idp.Manager GetIdpManager() idp.Manager
@@ -114,4 +114,5 @@ type Manager interface {
CreateAccountByPrivateDomain(ctx context.Context, initiatorId, domain string) (*types.Account, error) CreateAccountByPrivateDomain(ctx context.Context, initiatorId, domain string) (*types.Account, error)
UpdateToPrimaryAccount(ctx context.Context, accountId string) (*types.Account, error) UpdateToPrimaryAccount(ctx context.Context, accountId string) (*types.Account, error)
GetOwnerInfo(ctx context.Context, accountId string) (*types.UserInfo, error) GetOwnerInfo(ctx context.Context, accountId string) (*types.UserInfo, error)
GetCurrentUserInfo(ctx context.Context, accountID, userID string) (*types.UserInfo, error)
} }
+14 -14
View File
@@ -1115,7 +1115,7 @@ func TestAccountManager_NetworkUpdates_SaveGroup(t *testing.T) {
Name: "GroupA", Name: "GroupA",
Peers: []string{}, Peers: []string{},
} }
if err := manager.SaveGroup(context.Background(), account.Id, userID, &group); err != nil { if err := manager.SaveGroup(context.Background(), account.Id, userID, &group, true); err != nil {
t.Errorf("save group: %v", err) t.Errorf("save group: %v", err)
return return
} }
@@ -1131,7 +1131,7 @@ func TestAccountManager_NetworkUpdates_SaveGroup(t *testing.T) {
Action: types.PolicyTrafficActionAccept, Action: types.PolicyTrafficActionAccept,
}, },
}, },
}) }, true)
require.NoError(t, err) require.NoError(t, err)
updMsg := manager.peersUpdateManager.CreateChannel(context.Background(), peer1.ID) updMsg := manager.peersUpdateManager.CreateChannel(context.Background(), peer1.ID)
@@ -1150,7 +1150,7 @@ func TestAccountManager_NetworkUpdates_SaveGroup(t *testing.T) {
}() }()
group.Peers = []string{peer1.ID, peer2.ID, peer3.ID} group.Peers = []string{peer1.ID, peer2.ID, peer3.ID}
if err := manager.SaveGroup(context.Background(), account.Id, userID, &group); err != nil { if err := manager.SaveGroup(context.Background(), account.Id, userID, &group, true); err != nil {
t.Errorf("save group: %v", err) t.Errorf("save group: %v", err)
return return
} }
@@ -1192,7 +1192,7 @@ func TestAccountManager_NetworkUpdates_SavePolicy(t *testing.T) {
Name: "GroupA", Name: "GroupA",
Peers: []string{peer1.ID, peer2.ID}, Peers: []string{peer1.ID, peer2.ID},
} }
if err := manager.SaveGroup(context.Background(), account.Id, userID, &group); err != nil { if err := manager.SaveGroup(context.Background(), account.Id, userID, &group, true); err != nil {
t.Errorf("save group: %v", err) t.Errorf("save group: %v", err)
return return
} }
@@ -1223,7 +1223,7 @@ func TestAccountManager_NetworkUpdates_SavePolicy(t *testing.T) {
Action: types.PolicyTrafficActionAccept, Action: types.PolicyTrafficActionAccept,
}, },
}, },
}) }, true)
if err != nil { if err != nil {
t.Errorf("delete default rule: %v", err) t.Errorf("delete default rule: %v", err)
return return
@@ -1240,7 +1240,7 @@ func TestAccountManager_NetworkUpdates_DeletePeer(t *testing.T) {
Name: "GroupA", Name: "GroupA",
Peers: []string{peer1.ID, peer3.ID}, Peers: []string{peer1.ID, peer3.ID},
} }
if err := manager.SaveGroup(context.Background(), account.Id, userID, &group); err != nil { if err := manager.SaveGroup(context.Background(), account.Id, userID, &group, true); err != nil {
t.Errorf("save group: %v", err) t.Errorf("save group: %v", err)
return return
} }
@@ -1256,7 +1256,7 @@ func TestAccountManager_NetworkUpdates_DeletePeer(t *testing.T) {
Action: types.PolicyTrafficActionAccept, Action: types.PolicyTrafficActionAccept,
}, },
}, },
}) }, true)
if err != nil { if err != nil {
t.Errorf("save policy: %v", err) t.Errorf("save policy: %v", err)
return return
@@ -1295,7 +1295,7 @@ func TestAccountManager_NetworkUpdates_DeleteGroup(t *testing.T) {
ID: "groupA", ID: "groupA",
Name: "GroupA", Name: "GroupA",
Peers: []string{peer1.ID, peer2.ID, peer3.ID}, Peers: []string{peer1.ID, peer2.ID, peer3.ID},
}) }, true)
require.NoError(t, err, "failed to save group") require.NoError(t, err, "failed to save group")
@@ -1315,7 +1315,7 @@ func TestAccountManager_NetworkUpdates_DeleteGroup(t *testing.T) {
Action: types.PolicyTrafficActionAccept, Action: types.PolicyTrafficActionAccept,
}, },
}, },
}) }, true)
if err != nil { if err != nil {
t.Errorf("save policy: %v", err) t.Errorf("save policy: %v", err)
return return
@@ -2794,13 +2794,13 @@ func TestAccount_UserGroupsRemoveFromPeers(t *testing.T) {
}) })
} }
//type TB interface { // type TB interface {
// Cleanup(func()) // Cleanup(func())
// Helper() // Helper()
// TempDir() string // TempDir() string
// Errorf(format string, args ...interface{}) // Errorf(format string, args ...interface{})
// Fatalf(format string, args ...interface{}) // Fatalf(format string, args ...interface{})
//} // }
func createManager(t testing.TB) (*DefaultAccountManager, error) { func createManager(t testing.TB) (*DefaultAccountManager, error) {
t.Helper() t.Helper()
@@ -2816,8 +2816,6 @@ func createManager(t testing.TB) (*DefaultAccountManager, error) {
return nil, err return nil, err
} }
permissionsManagerMock := permissions.NewManagerMock()
ctrl := gomock.NewController(t) ctrl := gomock.NewController(t)
t.Cleanup(ctrl.Finish) t.Cleanup(ctrl.Finish)
@@ -2831,7 +2829,9 @@ func createManager(t testing.TB) (*DefaultAccountManager, error) {
Return(false, nil). Return(false, nil).
AnyTimes() AnyTimes()
manager, err := BuildManager(context.Background(), store, NewPeersUpdateManager(nil), nil, "", "netbird.cloud", eventStore, nil, false, MocIntegratedValidator{}, metrics, port_forwarding.NewControllerMock(), settingsMockManager, permissionsManagerMock) permissionsManager := permissions.NewManager(store)
manager, err := BuildManager(context.Background(), store, NewPeersUpdateManager(nil), nil, "", "netbird.cloud", eventStore, nil, false, MocIntegratedValidator{}, metrics, port_forwarding.NewControllerMock(), settingsMockManager, permissionsManager)
if err != nil { if err != nil {
return nil, err return nil, err
} }
+10 -18
View File
@@ -10,6 +10,8 @@ import (
nbdns "github.com/netbirdio/netbird/dns" nbdns "github.com/netbirdio/netbird/dns"
"github.com/netbirdio/netbird/management/proto" "github.com/netbirdio/netbird/management/proto"
"github.com/netbirdio/netbird/management/server/activity" "github.com/netbirdio/netbird/management/server/activity"
"github.com/netbirdio/netbird/management/server/permissions/modules"
"github.com/netbirdio/netbird/management/server/permissions/operations"
"github.com/netbirdio/netbird/management/server/status" "github.com/netbirdio/netbird/management/server/status"
"github.com/netbirdio/netbird/management/server/store" "github.com/netbirdio/netbird/management/server/store"
"github.com/netbirdio/netbird/management/server/types" "github.com/netbirdio/netbird/management/server/types"
@@ -62,17 +64,12 @@ func (c *DNSConfigCache) SetNameServerGroup(key string, value *proto.NameServerG
// GetDNSSettings validates a user role and returns the DNS settings for the provided account ID // GetDNSSettings validates a user role and returns the DNS settings for the provided account ID
func (am *DefaultAccountManager) GetDNSSettings(ctx context.Context, accountID string, userID string) (*types.DNSSettings, error) { func (am *DefaultAccountManager) GetDNSSettings(ctx context.Context, accountID string, userID string) (*types.DNSSettings, error) {
user, err := am.Store.GetUserByUserID(ctx, store.LockingStrengthShare, userID) allowed, err := am.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Dns, operations.Read)
if err != nil { if err != nil {
return nil, err return nil, status.NewPermissionValidationError(err)
} }
if !allowed {
if err := am.permissionsManager.ValidateAccountAccess(ctx, accountID, user, false); err != nil { return nil, status.NewPermissionDeniedError()
return nil, err
}
if user.IsRegularUser() {
return nil, status.NewAdminPermissionError()
} }
return am.Store.GetAccountDNSSettings(ctx, store.LockingStrengthShare, accountID) return am.Store.GetAccountDNSSettings(ctx, store.LockingStrengthShare, accountID)
@@ -84,17 +81,12 @@ func (am *DefaultAccountManager) SaveDNSSettings(ctx context.Context, accountID
return status.Errorf(status.InvalidArgument, "the dns settings provided are nil") return status.Errorf(status.InvalidArgument, "the dns settings provided are nil")
} }
user, err := am.Store.GetUserByUserID(ctx, store.LockingStrengthShare, userID) allowed, err := am.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Dns, operations.Update)
if err != nil { if err != nil {
return err return status.NewPermissionValidationError(err)
} }
if !allowed {
if err := am.permissionsManager.ValidateAccountAccess(ctx, accountID, user, false); err != nil { return status.NewPermissionDeniedError()
return err
}
if !user.HasAdminPower() {
return status.NewAdminPermissionError()
} }
var updateAccountPeers bool var updateAccountPeers bool
+4 -5
View File
@@ -211,14 +211,13 @@ func createDNSManager(t *testing.T) (*DefaultAccountManager, error) {
metrics, err := telemetry.NewDefaultAppMetrics(context.Background()) metrics, err := telemetry.NewDefaultAppMetrics(context.Background())
require.NoError(t, err) require.NoError(t, err)
permissionsManagerMock := permissions.NewManagerMock()
ctrl := gomock.NewController(t) ctrl := gomock.NewController(t)
t.Cleanup(ctrl.Finish) t.Cleanup(ctrl.Finish)
settingsMockManager := settings.NewMockManager(ctrl) settingsMockManager := settings.NewMockManager(ctrl)
permissionsManager := permissions.NewManager(store)
return BuildManager(context.Background(), store, NewPeersUpdateManager(nil), nil, "", "netbird.test", eventStore, nil, false, MocIntegratedValidator{}, metrics, port_forwarding.NewControllerMock(), settingsMockManager, permissionsManagerMock) return BuildManager(context.Background(), store, NewPeersUpdateManager(nil), nil, "", "netbird.test", eventStore, nil, false, MocIntegratedValidator{}, metrics, port_forwarding.NewControllerMock(), settingsMockManager, permissionsManager)
} }
func createDNSStore(t *testing.T) (store.Store, error) { func createDNSStore(t *testing.T) (store.Store, error) {
@@ -505,7 +504,7 @@ func TestDNSAccountPeersUpdate(t *testing.T) {
Name: "GroupB", Name: "GroupB",
Peers: []string{}, Peers: []string{},
}, },
}) }, true)
assert.NoError(t, err) assert.NoError(t, err)
updMsg := manager.peersUpdateManager.CreateChannel(context.Background(), peer1.ID) updMsg := manager.peersUpdateManager.CreateChannel(context.Background(), peer1.ID)
@@ -565,7 +564,7 @@ func TestDNSAccountPeersUpdate(t *testing.T) {
ID: "groupA", ID: "groupA",
Name: "GroupA", Name: "GroupA",
Peers: []string{peer1.ID, peer2.ID, peer3.ID}, Peers: []string{peer1.ID, peer2.ID, peer3.ID},
}) }, true)
assert.NoError(t, err) assert.NoError(t, err)
done := make(chan struct{}) done := make(chan struct{})
+14 -17
View File
@@ -9,6 +9,8 @@ import (
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/netbirdio/netbird/management/server/activity" "github.com/netbirdio/netbird/management/server/activity"
"github.com/netbirdio/netbird/management/server/permissions/modules"
"github.com/netbirdio/netbird/management/server/permissions/operations"
"github.com/netbirdio/netbird/management/server/status" "github.com/netbirdio/netbird/management/server/status"
"github.com/netbirdio/netbird/management/server/store" "github.com/netbirdio/netbird/management/server/store"
"github.com/netbirdio/netbird/management/server/types" "github.com/netbirdio/netbird/management/server/types"
@@ -21,17 +23,12 @@ func isEnabled() bool {
// GetEvents returns a list of activity events of an account // GetEvents returns a list of activity events of an account
func (am *DefaultAccountManager) GetEvents(ctx context.Context, accountID, userID string) ([]*activity.Event, error) { func (am *DefaultAccountManager) GetEvents(ctx context.Context, accountID, userID string) ([]*activity.Event, error) {
user, err := am.Store.GetUserByUserID(ctx, store.LockingStrengthShare, userID) allowed, err := am.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Events, operations.Read)
if err != nil { if err != nil {
return nil, err return nil, status.NewPermissionValidationError(err)
} }
if !allowed {
if err := am.permissionsManager.ValidateAccountAccess(ctx, accountID, user, false); err != nil { return nil, status.NewPermissionDeniedError()
return nil, err
}
if !(user.HasAdminPower() || user.IsServiceUser) {
return nil, status.Errorf(status.PermissionDenied, "only users with admin power can view events")
} }
events, err := am.eventStore.Get(ctx, accountID, 0, 10000, true) events, err := am.eventStore.Get(ctx, accountID, 0, 10000, true)
@@ -56,7 +53,7 @@ func (am *DefaultAccountManager) GetEvents(ctx context.Context, accountID, userI
filtered = append(filtered, event) filtered = append(filtered, event)
} }
err = am.fillEventsWithUserInfo(ctx, events, accountID, user) err = am.fillEventsWithUserInfo(ctx, events, accountID, userID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -89,8 +86,8 @@ type eventUserInfo struct {
accountId string accountId string
} }
func (am *DefaultAccountManager) fillEventsWithUserInfo(ctx context.Context, events []*activity.Event, accountId string, user *types.User) error { func (am *DefaultAccountManager) fillEventsWithUserInfo(ctx context.Context, events []*activity.Event, accountId string, userId string) error {
eventUserInfo, err := am.getEventsUserInfo(ctx, events, accountId, user) eventUserInfo, err := am.getEventsUserInfo(ctx, events, accountId, userId)
if err != nil { if err != nil {
return err return err
} }
@@ -105,14 +102,14 @@ func (am *DefaultAccountManager) fillEventsWithUserInfo(ctx context.Context, eve
return nil return nil
} }
func (am *DefaultAccountManager) getEventsUserInfo(ctx context.Context, events []*activity.Event, accountId string, user *types.User) (map[string]eventUserInfo, error) { func (am *DefaultAccountManager) getEventsUserInfo(ctx context.Context, events []*activity.Event, accountId string, userId string) (map[string]eventUserInfo, error) {
accountUsers, err := am.Store.GetAccountUsers(ctx, store.LockingStrengthShare, accountId) accountUsers, err := am.Store.GetAccountUsers(ctx, store.LockingStrengthShare, accountId)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// @note check whether using a external initiator user here is an issue // @note check whether using a external initiator user here is an issue
userInfos, err := am.BuildUserInfosForAccount(ctx, accountId, user.Id, accountUsers) userInfos, err := am.BuildUserInfosForAccount(ctx, accountId, userId, accountUsers)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -146,10 +143,10 @@ func (am *DefaultAccountManager) getEventsUserInfo(ctx context.Context, events [
return eventUserInfos, nil return eventUserInfos, nil
} }
return am.getEventsExternalUserInfo(ctx, externalUserIds, eventUserInfos, user) return am.getEventsExternalUserInfo(ctx, externalUserIds, eventUserInfos, userId)
} }
func (am *DefaultAccountManager) getEventsExternalUserInfo(ctx context.Context, externalUserIds []string, eventUserInfos map[string]eventUserInfo, user *types.User) (map[string]eventUserInfo, error) { func (am *DefaultAccountManager) getEventsExternalUserInfo(ctx context.Context, externalUserIds []string, eventUserInfos map[string]eventUserInfo, userId string) (map[string]eventUserInfo, error) {
externalAccountId := "" externalAccountId := ""
fetched := make(map[string]struct{}) fetched := make(map[string]struct{})
externalUsers := []*types.User{} externalUsers := []*types.User{}
@@ -182,7 +179,7 @@ func (am *DefaultAccountManager) getEventsExternalUserInfo(ctx context.Context,
return eventUserInfos, nil return eventUserInfos, nil
} }
externalUserInfos, err := am.BuildUserInfosForAccount(ctx, externalAccountId, user.Id, externalUsers) externalUserInfos, err := am.BuildUserInfosForAccount(ctx, externalAccountId, userId, externalUsers)
if err != nil { if err != nil {
return nil, err return nil, err
} }
+20 -28
View File
@@ -12,6 +12,8 @@ import (
nbdns "github.com/netbirdio/netbird/dns" nbdns "github.com/netbirdio/netbird/dns"
"github.com/netbirdio/netbird/management/server/activity" "github.com/netbirdio/netbird/management/server/activity"
routerTypes "github.com/netbirdio/netbird/management/server/networks/routers/types" routerTypes "github.com/netbirdio/netbird/management/server/networks/routers/types"
"github.com/netbirdio/netbird/management/server/permissions/modules"
"github.com/netbirdio/netbird/management/server/permissions/operations"
"github.com/netbirdio/netbird/management/server/status" "github.com/netbirdio/netbird/management/server/status"
"github.com/netbirdio/netbird/management/server/store" "github.com/netbirdio/netbird/management/server/store"
"github.com/netbirdio/netbird/management/server/types" "github.com/netbirdio/netbird/management/server/types"
@@ -30,17 +32,13 @@ func (e *GroupLinkError) Error() string {
// CheckGroupPermissions validates if a user has the necessary permissions to view groups // CheckGroupPermissions validates if a user has the necessary permissions to view groups
func (am *DefaultAccountManager) CheckGroupPermissions(ctx context.Context, accountID, userID string) error { func (am *DefaultAccountManager) CheckGroupPermissions(ctx context.Context, accountID, userID string) error {
user, err := am.Store.GetUserByUserID(ctx, store.LockingStrengthShare, userID) allowed, err := am.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Groups, operations.Read)
if err != nil { if err != nil {
return err return err
} }
if err := am.permissionsManager.ValidateAccountAccess(ctx, accountID, user, false); err != nil { if !allowed {
return err return status.NewPermissionDeniedError()
}
if user.IsRegularUser() {
return status.NewAdminPermissionError()
} }
return nil return nil
@@ -68,27 +66,26 @@ func (am *DefaultAccountManager) GetGroupByName(ctx context.Context, groupName,
} }
// SaveGroup object of the peers // SaveGroup object of the peers
func (am *DefaultAccountManager) SaveGroup(ctx context.Context, accountID, userID string, newGroup *types.Group) error { func (am *DefaultAccountManager) SaveGroup(ctx context.Context, accountID, userID string, newGroup *types.Group, create bool) error {
unlock := am.Store.AcquireWriteLockByUID(ctx, accountID) unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
defer unlock() defer unlock()
return am.SaveGroups(ctx, accountID, userID, []*types.Group{newGroup}) return am.SaveGroups(ctx, accountID, userID, []*types.Group{newGroup}, create)
} }
// SaveGroups adds new groups to the account. // SaveGroups adds new groups to the account.
// Note: This function does not acquire the global lock. // Note: This function does not acquire the global lock.
// It is the caller's responsibility to ensure proper locking is in place before invoking this method. // It is the caller's responsibility to ensure proper locking is in place before invoking this method.
func (am *DefaultAccountManager) SaveGroups(ctx context.Context, accountID, userID string, groups []*types.Group) error { func (am *DefaultAccountManager) SaveGroups(ctx context.Context, accountID, userID string, groups []*types.Group, create bool) error {
user, err := am.Store.GetUserByUserID(ctx, store.LockingStrengthShare, userID) operation := operations.Create
if !create {
operation = operations.Update
}
allowed, err := am.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Groups, operation)
if err != nil { if err != nil {
return err return status.NewPermissionValidationError(err)
} }
if !allowed {
if err := am.permissionsManager.ValidateAccountAccess(ctx, accountID, user, false); err != nil { return status.NewPermissionDeniedError()
return err
}
if user.IsRegularUser() {
return status.NewAdminPermissionError()
} }
var eventsToStore []func() var eventsToStore []func()
@@ -210,17 +207,12 @@ func (am *DefaultAccountManager) DeleteGroup(ctx context.Context, accountID, use
// If an error occurs while deleting a group, the function skips it and continues deleting other groups. // If an error occurs while deleting a group, the function skips it and continues deleting other groups.
// Errors are collected and returned at the end. // Errors are collected and returned at the end.
func (am *DefaultAccountManager) DeleteGroups(ctx context.Context, accountID, userID string, groupIDs []string) error { func (am *DefaultAccountManager) DeleteGroups(ctx context.Context, accountID, userID string, groupIDs []string) error {
user, err := am.Store.GetUserByUserID(ctx, store.LockingStrengthShare, userID) allowed, err := am.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Groups, operations.Delete)
if err != nil { if err != nil {
return err return status.NewPermissionValidationError(err)
} }
if !allowed {
if err := am.permissionsManager.ValidateAccountAccess(ctx, accountID, user, false); err != nil { return status.NewPermissionDeniedError()
return err
}
if user.IsRegularUser() {
return status.NewAdminPermissionError()
} }
var allErrors error var allErrors error
+19 -19
View File
@@ -40,7 +40,7 @@ func TestDefaultAccountManager_CreateGroup(t *testing.T) {
} }
for _, group := range account.Groups { for _, group := range account.Groups {
group.Issued = types.GroupIssuedIntegration group.Issued = types.GroupIssuedIntegration
err = am.SaveGroup(context.Background(), account.Id, groupAdminUserID, group) err = am.SaveGroup(context.Background(), account.Id, groupAdminUserID, group, true)
if err != nil { if err != nil {
t.Errorf("should allow to create %s groups", types.GroupIssuedIntegration) t.Errorf("should allow to create %s groups", types.GroupIssuedIntegration)
} }
@@ -48,7 +48,7 @@ func TestDefaultAccountManager_CreateGroup(t *testing.T) {
for _, group := range account.Groups { for _, group := range account.Groups {
group.Issued = types.GroupIssuedJWT group.Issued = types.GroupIssuedJWT
err = am.SaveGroup(context.Background(), account.Id, groupAdminUserID, group) err = am.SaveGroup(context.Background(), account.Id, groupAdminUserID, group, true)
if err != nil { if err != nil {
t.Errorf("should allow to create %s groups", types.GroupIssuedJWT) t.Errorf("should allow to create %s groups", types.GroupIssuedJWT)
} }
@@ -56,7 +56,7 @@ func TestDefaultAccountManager_CreateGroup(t *testing.T) {
for _, group := range account.Groups { for _, group := range account.Groups {
group.Issued = types.GroupIssuedAPI group.Issued = types.GroupIssuedAPI
group.ID = "" group.ID = ""
err = am.SaveGroup(context.Background(), account.Id, groupAdminUserID, group) err = am.SaveGroup(context.Background(), account.Id, groupAdminUserID, group, true)
if err == nil { if err == nil {
t.Errorf("should not create api group with the same name, %s", group.Name) t.Errorf("should not create api group with the same name, %s", group.Name)
} }
@@ -162,7 +162,7 @@ func TestDefaultAccountManager_DeleteGroups(t *testing.T) {
} }
} }
err = manager.SaveGroups(context.Background(), account.Id, groupAdminUserID, groups) err = manager.SaveGroups(context.Background(), account.Id, groupAdminUserID, groups, true)
assert.NoError(t, err, "Failed to save test groups") assert.NoError(t, err, "Failed to save test groups")
testCases := []struct { testCases := []struct {
@@ -382,13 +382,13 @@ func initTestGroupAccount(am *DefaultAccountManager) (*DefaultAccountManager, *t
return nil, nil, err return nil, nil, err
} }
_ = am.SaveGroup(context.Background(), accountID, groupAdminUserID, groupForRoute) _ = am.SaveGroup(context.Background(), accountID, groupAdminUserID, groupForRoute, true)
_ = am.SaveGroup(context.Background(), accountID, groupAdminUserID, groupForRoute2) _ = am.SaveGroup(context.Background(), accountID, groupAdminUserID, groupForRoute2, true)
_ = am.SaveGroup(context.Background(), accountID, groupAdminUserID, groupForNameServerGroups) _ = am.SaveGroup(context.Background(), accountID, groupAdminUserID, groupForNameServerGroups, true)
_ = am.SaveGroup(context.Background(), accountID, groupAdminUserID, groupForPolicies) _ = am.SaveGroup(context.Background(), accountID, groupAdminUserID, groupForPolicies, true)
_ = am.SaveGroup(context.Background(), accountID, groupAdminUserID, groupForSetupKeys) _ = am.SaveGroup(context.Background(), accountID, groupAdminUserID, groupForSetupKeys, true)
_ = am.SaveGroup(context.Background(), accountID, groupAdminUserID, groupForUsers) _ = am.SaveGroup(context.Background(), accountID, groupAdminUserID, groupForUsers, true)
_ = am.SaveGroup(context.Background(), accountID, groupAdminUserID, groupForIntegration) _ = am.SaveGroup(context.Background(), accountID, groupAdminUserID, groupForIntegration, true)
acc, err := am.Store.GetAccount(context.Background(), account.Id) acc, err := am.Store.GetAccount(context.Background(), account.Id)
if err != nil { if err != nil {
@@ -426,7 +426,7 @@ func TestGroupAccountPeersUpdate(t *testing.T) {
Name: "GroupE", Name: "GroupE",
Peers: []string{peer2.ID}, Peers: []string{peer2.ID},
}, },
}) }, true)
assert.NoError(t, err) assert.NoError(t, err)
updMsg := manager.peersUpdateManager.CreateChannel(context.Background(), peer1.ID) updMsg := manager.peersUpdateManager.CreateChannel(context.Background(), peer1.ID)
@@ -446,7 +446,7 @@ func TestGroupAccountPeersUpdate(t *testing.T) {
ID: "groupB", ID: "groupB",
Name: "GroupB", Name: "GroupB",
Peers: []string{peer1.ID, peer2.ID}, Peers: []string{peer1.ID, peer2.ID},
}) }, true)
assert.NoError(t, err) assert.NoError(t, err)
select { select {
@@ -524,7 +524,7 @@ func TestGroupAccountPeersUpdate(t *testing.T) {
Action: types.PolicyTrafficActionAccept, Action: types.PolicyTrafficActionAccept,
}, },
}, },
}) }, true)
assert.NoError(t, err) assert.NoError(t, err)
// Saving a group linked to policy should update account peers and send peer update // Saving a group linked to policy should update account peers and send peer update
@@ -539,7 +539,7 @@ func TestGroupAccountPeersUpdate(t *testing.T) {
ID: "groupA", ID: "groupA",
Name: "GroupA", Name: "GroupA",
Peers: []string{peer1.ID, peer2.ID}, Peers: []string{peer1.ID, peer2.ID},
}) }, true)
assert.NoError(t, err) assert.NoError(t, err)
select { select {
@@ -608,7 +608,7 @@ func TestGroupAccountPeersUpdate(t *testing.T) {
ID: "groupC", ID: "groupC",
Name: "GroupC", Name: "GroupC",
Peers: []string{peer1.ID, peer3.ID}, Peers: []string{peer1.ID, peer3.ID},
}) }, true)
assert.NoError(t, err) assert.NoError(t, err)
select { select {
@@ -649,7 +649,7 @@ func TestGroupAccountPeersUpdate(t *testing.T) {
ID: "groupA", ID: "groupA",
Name: "GroupA", Name: "GroupA",
Peers: []string{peer1.ID, peer2.ID, peer3.ID}, Peers: []string{peer1.ID, peer2.ID, peer3.ID},
}) }, true)
assert.NoError(t, err) assert.NoError(t, err)
select { select {
@@ -676,7 +676,7 @@ func TestGroupAccountPeersUpdate(t *testing.T) {
ID: "groupD", ID: "groupD",
Name: "GroupD", Name: "GroupD",
Peers: []string{peer1.ID}, Peers: []string{peer1.ID},
}) }, true)
assert.NoError(t, err) assert.NoError(t, err)
select { select {
@@ -723,7 +723,7 @@ func TestGroupAccountPeersUpdate(t *testing.T) {
ID: "groupE", ID: "groupE",
Name: "GroupE", Name: "GroupE",
Peers: []string{peer2.ID, peer3.ID}, Peers: []string{peer2.ID, peer3.ID},
}) }, true)
assert.NoError(t, err) assert.NoError(t, err)
select { select {
+4 -2
View File
@@ -8,6 +8,8 @@ import (
"github.com/netbirdio/netbird/management/server/activity" "github.com/netbirdio/netbird/management/server/activity"
"github.com/netbirdio/netbird/management/server/http/api" "github.com/netbirdio/netbird/management/server/http/api"
"github.com/netbirdio/netbird/management/server/permissions" "github.com/netbirdio/netbird/management/server/permissions"
"github.com/netbirdio/netbird/management/server/permissions/modules"
"github.com/netbirdio/netbird/management/server/permissions/operations"
"github.com/netbirdio/netbird/management/server/store" "github.com/netbirdio/netbird/management/server/store"
"github.com/netbirdio/netbird/management/server/types" "github.com/netbirdio/netbird/management/server/types"
) )
@@ -39,7 +41,7 @@ func NewManager(store store.Store, permissionsManager permissions.Manager, accou
} }
func (m *managerImpl) GetAllGroups(ctx context.Context, accountID, userID string) ([]*types.Group, error) { func (m *managerImpl) GetAllGroups(ctx context.Context, accountID, userID string) ([]*types.Group, error) {
ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, permissions.Groups, permissions.Read) ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Groups, operations.Read)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -70,7 +72,7 @@ func (m *managerImpl) GetAllGroupsMap(ctx context.Context, accountID, userID str
} }
func (m *managerImpl) AddResourceToGroup(ctx context.Context, accountID, userID, groupID string, resource *types.Resource) error { func (m *managerImpl) AddResourceToGroup(ctx context.Context, accountID, userID, groupID string, resource *types.Resource) error {
ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, permissions.Groups, permissions.Write) ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Groups, operations.Update)
if err != nil { if err != nil {
return err return err
} }
+1
View File
@@ -828,6 +828,7 @@ func (s *GRPCServer) GetPKCEAuthorizationFlow(ctx context.Context, req *proto.En
Scope: s.config.PKCEAuthorizationFlow.ProviderConfig.Scope, Scope: s.config.PKCEAuthorizationFlow.ProviderConfig.Scope,
RedirectURLs: s.config.PKCEAuthorizationFlow.ProviderConfig.RedirectURLs, RedirectURLs: s.config.PKCEAuthorizationFlow.ProviderConfig.RedirectURLs,
UseIDToken: s.config.PKCEAuthorizationFlow.ProviderConfig.UseIDToken, UseIDToken: s.config.PKCEAuthorizationFlow.ProviderConfig.UseIDToken,
DisablePromptLogin: s.config.PKCEAuthorizationFlow.ProviderConfig.DisablePromptLogin,
}, },
} }
+23
View File
@@ -2397,6 +2397,29 @@ paths:
"$ref": "#/components/responses/forbidden" "$ref": "#/components/responses/forbidden"
'500': '500':
"$ref": "#/components/responses/internal_error" "$ref": "#/components/responses/internal_error"
/api/users/current:
get:
summary: Retrieve current user
description: Get information about the current user
tags: [ Users ]
security:
- BearerAuth: [ ]
- TokenAuth: [ ]
responses:
'200':
description: A User object
content:
application/json:
schema:
$ref: '#/components/schemas/User'
'400':
"$ref": "#/components/responses/bad_request"
'401':
"$ref": "#/components/responses/requires_authentication"
'403':
"$ref": "#/components/responses/forbidden"
'500':
"$ref": "#/components/responses/internal_error"
/api/peers: /api/peers:
get: get:
summary: List all Peers summary: List all Peers
+1 -1
View File
@@ -230,7 +230,7 @@ type Account struct {
// AccountExtraSettings defines model for AccountExtraSettings. // AccountExtraSettings defines model for AccountExtraSettings.
type AccountExtraSettings struct { type AccountExtraSettings struct {
// NetworkTrafficLogsEnabled Enables or disables network traffic logs. If enabled, all network traffic logs from peers will be stored. // NetworkTrafficLogsEnabled Enables or disables network traffic logging. If enabled, all network traffic events from peers will be stored.
NetworkTrafficLogsEnabled bool `json:"network_traffic_logs_enabled"` NetworkTrafficLogsEnabled bool `json:"network_traffic_logs_enabled"`
// NetworkTrafficPacketCounterEnabled Enables or disables network traffic packet counter. If enabled, network packets and their size will be counted and reported. (This can have an slight impact on performance) // NetworkTrafficPacketCounterEnabled Enables or disables network traffic packet counter. If enabled, network packets and their size will be counted and reported. (This can have an slight impact on performance)
+3 -3
View File
@@ -66,15 +66,13 @@ func NewAPIHandler(
corsMiddleware := cors.AllowAll() corsMiddleware := cors.AllowAll()
acMiddleware := middleware.NewAccessControl(accountManager.GetUserFromUserAuth)
rootRouter := mux.NewRouter() rootRouter := mux.NewRouter()
metricsMiddleware := appMetrics.HTTPMiddleware() metricsMiddleware := appMetrics.HTTPMiddleware()
prefix := apiPrefix prefix := apiPrefix
router := rootRouter.PathPrefix(prefix).Subrouter() router := rootRouter.PathPrefix(prefix).Subrouter()
router.Use(metricsMiddleware.Handler, corsMiddleware.Handler, authMiddleware.Handler, acMiddleware.Handler) router.Use(metricsMiddleware.Handler, corsMiddleware.Handler, authMiddleware.Handler)
if _, err := integrations.RegisterHandlers(ctx, prefix, router, accountManager, integratedValidator, appMetrics.GetMeter(), permissionsManager, peersManager, proxyController, settingsManager); err != nil { if _, err := integrations.RegisterHandlers(ctx, prefix, router, accountManager, integratedValidator, appMetrics.GetMeter(), permissionsManager, peersManager, proxyController, settingsManager); err != nil {
return nil, fmt.Errorf("register integrations endpoints: %w", err) return nil, fmt.Errorf("register integrations endpoints: %w", err)
@@ -85,6 +83,8 @@ func NewAPIHandler(
users.AddEndpoints(accountManager, router) users.AddEndpoints(accountManager, router)
setup_keys.AddEndpoints(accountManager, router) setup_keys.AddEndpoints(accountManager, router)
policies.AddEndpoints(accountManager, LocationManager, router) policies.AddEndpoints(accountManager, LocationManager, router)
policies.AddPostureCheckEndpoints(accountManager, LocationManager, router)
policies.AddLocationsEndpoints(accountManager, LocationManager, permissionsManager, router)
groups.AddEndpoints(accountManager, router) groups.AddEndpoints(accountManager, router)
routes.AddEndpoints(accountManager, router) routes.AddEndpoints(accountManager, router)
dns.AddEndpoints(accountManager, router) dns.AddEndpoints(accountManager, router)
@@ -143,7 +143,7 @@ func (h *handler) updateGroup(w http.ResponseWriter, r *http.Request) {
IntegrationReference: existingGroup.IntegrationReference, IntegrationReference: existingGroup.IntegrationReference,
} }
if err := h.accountManager.SaveGroup(r.Context(), accountID, userID, &group); err != nil { if err := h.accountManager.SaveGroup(r.Context(), accountID, userID, &group, false); err != nil {
log.WithContext(r.Context()).Errorf("failed updating group %s under account %s %v", groupID, accountID, err) log.WithContext(r.Context()).Errorf("failed updating group %s under account %s %v", groupID, accountID, err)
util.WriteError(r.Context(), err, w) util.WriteError(r.Context(), err, w)
return return
@@ -203,7 +203,7 @@ func (h *handler) createGroup(w http.ResponseWriter, r *http.Request) {
Issued: types.GroupIssuedAPI, Issued: types.GroupIssuedAPI,
} }
err = h.accountManager.SaveGroup(r.Context(), accountID, userID, &group) err = h.accountManager.SaveGroup(r.Context(), accountID, userID, &group, true)
if err != nil { if err != nil {
util.WriteError(r.Context(), err, w) util.WriteError(r.Context(), err, w)
return return
@@ -35,7 +35,7 @@ var TestPeers = map[string]*nbpeer.Peer{
func initGroupTestData(initGroups ...*types.Group) *handler { func initGroupTestData(initGroups ...*types.Group) *handler {
return &handler{ return &handler{
accountManager: &mock_server.MockAccountManager{ accountManager: &mock_server.MockAccountManager{
SaveGroupFunc: func(_ context.Context, accountID, userID string, group *types.Group) error { SaveGroupFunc: func(_ context.Context, accountID, userID string, group *types.Group, create bool) error {
if !strings.HasPrefix(group.ID, "id-") { if !strings.HasPrefix(group.ID, "id-") {
group.ID = "id-was-set" group.ID = "id-was-set"
} }
@@ -10,6 +10,7 @@ import (
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/netbirdio/netbird/management/server/account" "github.com/netbirdio/netbird/management/server/account"
"github.com/netbirdio/netbird/management/server/activity"
nbcontext "github.com/netbirdio/netbird/management/server/context" nbcontext "github.com/netbirdio/netbird/management/server/context"
"github.com/netbirdio/netbird/management/server/groups" "github.com/netbirdio/netbird/management/server/groups"
"github.com/netbirdio/netbird/management/server/http/api" "github.com/netbirdio/netbird/management/server/http/api"
@@ -244,7 +245,7 @@ func (h *Handler) GetAccessiblePeers(w http.ResponseWriter, r *http.Request) {
return return
} }
account, err := h.accountManager.GetAccountByID(r.Context(), accountID, userID) account, err := h.accountManager.GetAccountByID(r.Context(), accountID, activity.SystemInitiator)
if err != nil { if err != nil {
util.WriteError(r.Context(), err, w) util.WriteError(r.Context(), err, w)
return return
@@ -10,6 +10,7 @@ import (
"path/filepath" "path/filepath"
"testing" "testing"
"github.com/golang/mock/gomock"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@@ -17,6 +18,9 @@ import (
"github.com/netbirdio/netbird/management/server/geolocation" "github.com/netbirdio/netbird/management/server/geolocation"
"github.com/netbirdio/netbird/management/server/http/api" "github.com/netbirdio/netbird/management/server/http/api"
"github.com/netbirdio/netbird/management/server/mock_server" "github.com/netbirdio/netbird/management/server/mock_server"
"github.com/netbirdio/netbird/management/server/permissions"
"github.com/netbirdio/netbird/management/server/permissions/modules"
"github.com/netbirdio/netbird/management/server/permissions/operations"
"github.com/netbirdio/netbird/management/server/types" "github.com/netbirdio/netbird/management/server/types"
"github.com/netbirdio/netbird/util" "github.com/netbirdio/netbird/util"
) )
@@ -41,6 +45,14 @@ func initGeolocationTestData(t *testing.T) *geolocationsHandler {
assert.NoError(t, err) assert.NoError(t, err)
t.Cleanup(func() { _ = geo.Stop() }) t.Cleanup(func() { _ = geo.Stop() })
ctrl := gomock.NewController(t)
permissionsManagerMock := permissions.NewMockManager(ctrl)
permissionsManagerMock.
EXPECT().
ValidateUserPermissions(gomock.Any(), gomock.Any(), gomock.Any(), modules.Policies, operations.Read).
Return(true, nil).
AnyTimes()
return &geolocationsHandler{ return &geolocationsHandler{
accountManager: &mock_server.MockAccountManager{ accountManager: &mock_server.MockAccountManager{
GetUserByIDFunc: func(ctx context.Context, id string) (*types.User, error) { GetUserByIDFunc: func(ctx context.Context, id string) (*types.User, error) {
@@ -48,6 +60,7 @@ func initGeolocationTestData(t *testing.T) *geolocationsHandler {
}, },
}, },
geolocationManager: geo, geolocationManager: geo,
permissionsManager: permissionsManagerMock,
} }
} }
@@ -11,6 +11,9 @@ import (
"github.com/netbirdio/netbird/management/server/geolocation" "github.com/netbirdio/netbird/management/server/geolocation"
"github.com/netbirdio/netbird/management/server/http/api" "github.com/netbirdio/netbird/management/server/http/api"
"github.com/netbirdio/netbird/management/server/http/util" "github.com/netbirdio/netbird/management/server/http/util"
"github.com/netbirdio/netbird/management/server/permissions"
"github.com/netbirdio/netbird/management/server/permissions/modules"
"github.com/netbirdio/netbird/management/server/permissions/operations"
"github.com/netbirdio/netbird/management/server/status" "github.com/netbirdio/netbird/management/server/status"
) )
@@ -22,19 +25,21 @@ var (
type geolocationsHandler struct { type geolocationsHandler struct {
accountManager account.Manager accountManager account.Manager
geolocationManager geolocation.Geolocation geolocationManager geolocation.Geolocation
permissionsManager permissions.Manager
} }
func addLocationsEndpoint(accountManager account.Manager, locationManager geolocation.Geolocation, router *mux.Router) { func AddLocationsEndpoints(accountManager account.Manager, locationManager geolocation.Geolocation, permissionsManager permissions.Manager, router *mux.Router) {
locationHandler := newGeolocationsHandlerHandler(accountManager, locationManager) locationHandler := newGeolocationsHandlerHandler(accountManager, locationManager, permissionsManager)
router.HandleFunc("/locations/countries", locationHandler.getAllCountries).Methods("GET", "OPTIONS") router.HandleFunc("/locations/countries", locationHandler.getAllCountries).Methods("GET", "OPTIONS")
router.HandleFunc("/locations/countries/{country}/cities", locationHandler.getCitiesByCountry).Methods("GET", "OPTIONS") router.HandleFunc("/locations/countries/{country}/cities", locationHandler.getCitiesByCountry).Methods("GET", "OPTIONS")
} }
// newGeolocationsHandlerHandler creates a new Geolocations handler // newGeolocationsHandlerHandler creates a new Geolocations handler
func newGeolocationsHandlerHandler(accountManager account.Manager, geolocationManager geolocation.Geolocation) *geolocationsHandler { func newGeolocationsHandlerHandler(accountManager account.Manager, geolocationManager geolocation.Geolocation, permissionsManager permissions.Manager) *geolocationsHandler {
return &geolocationsHandler{ return &geolocationsHandler{
accountManager: accountManager, accountManager: accountManager,
geolocationManager: geolocationManager, geolocationManager: geolocationManager,
permissionsManager: permissionsManager,
} }
} }
@@ -98,20 +103,22 @@ func (l *geolocationsHandler) getCitiesByCountry(w http.ResponseWriter, r *http.
} }
func (l *geolocationsHandler) authenticateUser(r *http.Request) error { func (l *geolocationsHandler) authenticateUser(r *http.Request) error {
userAuth, err := nbcontext.GetUserAuthFromContext(r.Context()) ctx := r.Context()
userAuth, err := nbcontext.GetUserAuthFromContext(ctx)
if err != nil { if err != nil {
return err return err
} }
_, userID := userAuth.AccountId, userAuth.UserId accountID, userID := userAuth.AccountId, userAuth.UserId
user, err := l.accountManager.GetUserByID(r.Context(), userID) allowed, err := l.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Policies, operations.Read)
if err != nil { if err != nil {
return err return status.NewPermissionValidationError(err)
} }
if !user.HasAdminPower() { if !allowed {
return status.Errorf(status.PermissionDenied, "user is not allowed to perform this action") return status.NewPermissionDeniedError()
} }
return nil return nil
} }
@@ -28,7 +28,6 @@ func AddEndpoints(accountManager account.Manager, locationManager geolocation.Ge
router.HandleFunc("/policies/{policyId}", policiesHandler.updatePolicy).Methods("PUT", "OPTIONS") router.HandleFunc("/policies/{policyId}", policiesHandler.updatePolicy).Methods("PUT", "OPTIONS")
router.HandleFunc("/policies/{policyId}", policiesHandler.getPolicy).Methods("GET", "OPTIONS") router.HandleFunc("/policies/{policyId}", policiesHandler.getPolicy).Methods("GET", "OPTIONS")
router.HandleFunc("/policies/{policyId}", policiesHandler.deletePolicy).Methods("DELETE", "OPTIONS") router.HandleFunc("/policies/{policyId}", policiesHandler.deletePolicy).Methods("DELETE", "OPTIONS")
addPostureCheckEndpoint(accountManager, locationManager, router)
} }
// newHandler creates a new policies handler // newHandler creates a new policies handler
@@ -96,7 +95,7 @@ func (h *handler) updatePolicy(w http.ResponseWriter, r *http.Request) {
return return
} }
h.savePolicy(w, r, accountID, userID, policyID) h.savePolicy(w, r, accountID, userID, policyID, false)
} }
// createPolicy handles policy creation request // createPolicy handles policy creation request
@@ -109,11 +108,11 @@ func (h *handler) createPolicy(w http.ResponseWriter, r *http.Request) {
accountID, userID := userAuth.AccountId, userAuth.UserId accountID, userID := userAuth.AccountId, userAuth.UserId
h.savePolicy(w, r, accountID, userID, "") h.savePolicy(w, r, accountID, userID, "", true)
} }
// savePolicy handles policy creation and update // savePolicy handles policy creation and update
func (h *handler) savePolicy(w http.ResponseWriter, r *http.Request, accountID string, userID string, policyID string) { func (h *handler) savePolicy(w http.ResponseWriter, r *http.Request, accountID string, userID string, policyID string, create bool) {
var req api.PutApiPoliciesPolicyIdJSONRequestBody var req api.PutApiPoliciesPolicyIdJSONRequestBody
if err := json.NewDecoder(r.Body).Decode(&req); err != nil { if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
util.WriteErrorResponse("couldn't parse JSON request", http.StatusBadRequest, w) util.WriteErrorResponse("couldn't parse JSON request", http.StatusBadRequest, w)
@@ -280,7 +279,7 @@ func (h *handler) savePolicy(w http.ResponseWriter, r *http.Request, accountID s
policy.SourcePostureChecks = *req.SourcePostureChecks policy.SourcePostureChecks = *req.SourcePostureChecks
} }
policy, err := h.accountManager.SavePolicy(r.Context(), accountID, userID, policy) policy, err := h.accountManager.SavePolicy(r.Context(), accountID, userID, policy, create)
if err != nil { if err != nil {
util.WriteError(r.Context(), err, w) util.WriteError(r.Context(), err, w)
return return
@@ -34,7 +34,7 @@ func initPoliciesTestData(policies ...*types.Policy) *handler {
} }
return policy, nil return policy, nil
}, },
SavePolicyFunc: func(_ context.Context, _, _ string, policy *types.Policy) (*types.Policy, error) { SavePolicyFunc: func(_ context.Context, _, _ string, policy *types.Policy, create bool) (*types.Policy, error) {
if !strings.HasPrefix(policy.ID, "id-") { if !strings.HasPrefix(policy.ID, "id-") {
policy.ID = "id-was-set" policy.ID = "id-was-set"
policy.Rules[0].ID = "id-was-set" policy.Rules[0].ID = "id-was-set"
@@ -21,14 +21,13 @@ type postureChecksHandler struct {
geolocationManager geolocation.Geolocation geolocationManager geolocation.Geolocation
} }
func addPostureCheckEndpoint(accountManager account.Manager, locationManager geolocation.Geolocation, router *mux.Router) { func AddPostureCheckEndpoints(accountManager account.Manager, locationManager geolocation.Geolocation, router *mux.Router) {
postureCheckHandler := newPostureChecksHandler(accountManager, locationManager) postureCheckHandler := newPostureChecksHandler(accountManager, locationManager)
router.HandleFunc("/posture-checks", postureCheckHandler.getAllPostureChecks).Methods("GET", "OPTIONS") router.HandleFunc("/posture-checks", postureCheckHandler.getAllPostureChecks).Methods("GET", "OPTIONS")
router.HandleFunc("/posture-checks", postureCheckHandler.createPostureCheck).Methods("POST", "OPTIONS") router.HandleFunc("/posture-checks", postureCheckHandler.createPostureCheck).Methods("POST", "OPTIONS")
router.HandleFunc("/posture-checks/{postureCheckId}", postureCheckHandler.updatePostureCheck).Methods("PUT", "OPTIONS") router.HandleFunc("/posture-checks/{postureCheckId}", postureCheckHandler.updatePostureCheck).Methods("PUT", "OPTIONS")
router.HandleFunc("/posture-checks/{postureCheckId}", postureCheckHandler.getPostureCheck).Methods("GET", "OPTIONS") router.HandleFunc("/posture-checks/{postureCheckId}", postureCheckHandler.getPostureCheck).Methods("GET", "OPTIONS")
router.HandleFunc("/posture-checks/{postureCheckId}", postureCheckHandler.deletePostureCheck).Methods("DELETE", "OPTIONS") router.HandleFunc("/posture-checks/{postureCheckId}", postureCheckHandler.deletePostureCheck).Methods("DELETE", "OPTIONS")
addLocationsEndpoint(accountManager, locationManager, router)
} }
// newPostureChecksHandler creates a new PostureChecks handler // newPostureChecksHandler creates a new PostureChecks handler
@@ -85,7 +84,7 @@ func (p *postureChecksHandler) updatePostureCheck(w http.ResponseWriter, r *http
return return
} }
p.savePostureChecks(w, r, accountID, userID, postureChecksID) p.savePostureChecks(w, r, accountID, userID, postureChecksID, false)
} }
// createPostureCheck handles posture check creation request // createPostureCheck handles posture check creation request
@@ -98,7 +97,7 @@ func (p *postureChecksHandler) createPostureCheck(w http.ResponseWriter, r *http
accountID, userID := userAuth.AccountId, userAuth.UserId accountID, userID := userAuth.AccountId, userAuth.UserId
p.savePostureChecks(w, r, accountID, userID, "") p.savePostureChecks(w, r, accountID, userID, "", true)
} }
// getPostureCheck handles a posture check Get request identified by ID // getPostureCheck handles a posture check Get request identified by ID
@@ -151,7 +150,7 @@ func (p *postureChecksHandler) deletePostureCheck(w http.ResponseWriter, r *http
} }
// savePostureChecks handles posture checks create and update // savePostureChecks handles posture checks create and update
func (p *postureChecksHandler) savePostureChecks(w http.ResponseWriter, r *http.Request, accountID, userID, postureChecksID string) { func (p *postureChecksHandler) savePostureChecks(w http.ResponseWriter, r *http.Request, accountID, userID, postureChecksID string, create bool) {
var ( var (
err error err error
req api.PostureCheckUpdate req api.PostureCheckUpdate
@@ -176,7 +175,7 @@ func (p *postureChecksHandler) savePostureChecks(w http.ResponseWriter, r *http.
return return
} }
postureChecks, err = p.accountManager.SavePostureChecks(r.Context(), accountID, userID, postureChecks) postureChecks, err = p.accountManager.SavePostureChecks(r.Context(), accountID, userID, postureChecks, create)
if err != nil { if err != nil {
util.WriteError(r.Context(), err, w) util.WriteError(r.Context(), err, w)
return return
@@ -40,7 +40,7 @@ func initPostureChecksTestData(postureChecks ...*posture.Checks) *postureChecksH
} }
return p, nil return p, nil
}, },
SavePostureChecksFunc: func(_ context.Context, accountID, userID string, postureChecks *posture.Checks) (*posture.Checks, error) { SavePostureChecksFunc: func(_ context.Context, accountID, userID string, postureChecks *posture.Checks, create bool) (*posture.Checks, error) {
postureChecks.ID = "postureCheck" postureChecks.ID = "postureCheck"
testPostureChecks[postureChecks.ID] = postureChecks testPostureChecks[postureChecks.ID] = postureChecks
@@ -25,6 +25,7 @@ type handler struct {
func AddEndpoints(accountManager account.Manager, router *mux.Router) { func AddEndpoints(accountManager account.Manager, router *mux.Router) {
userHandler := newHandler(accountManager) userHandler := newHandler(accountManager)
router.HandleFunc("/users", userHandler.getAllUsers).Methods("GET", "OPTIONS") router.HandleFunc("/users", userHandler.getAllUsers).Methods("GET", "OPTIONS")
router.HandleFunc("/users/current", userHandler.getCurrentUser).Methods("GET", "OPTIONS")
router.HandleFunc("/users/{userId}", userHandler.updateUser).Methods("PUT", "OPTIONS") router.HandleFunc("/users/{userId}", userHandler.updateUser).Methods("PUT", "OPTIONS")
router.HandleFunc("/users/{userId}", userHandler.deleteUser).Methods("DELETE", "OPTIONS") router.HandleFunc("/users/{userId}", userHandler.deleteUser).Methods("DELETE", "OPTIONS")
router.HandleFunc("/users", userHandler.createUser).Methods("POST", "OPTIONS") router.HandleFunc("/users", userHandler.createUser).Methods("POST", "OPTIONS")
@@ -259,6 +260,29 @@ func (h *handler) inviteUser(w http.ResponseWriter, r *http.Request) {
util.WriteJSONObject(r.Context(), w, util.EmptyObject{}) util.WriteJSONObject(r.Context(), w, util.EmptyObject{})
} }
func (h *handler) getCurrentUser(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
util.WriteErrorResponse("wrong HTTP method", http.StatusMethodNotAllowed, w)
return
}
ctx := r.Context()
userAuth, err := nbcontext.GetUserAuthFromContext(ctx)
if err != nil {
util.WriteError(r.Context(), err, w)
return
}
accountID, userID := userAuth.AccountId, userAuth.UserId
user, err := h.accountManager.GetCurrentUserInfo(ctx, accountID, userID)
if err != nil {
util.WriteError(r.Context(), err, w)
return
}
util.WriteJSONObject(r.Context(), w, toUserResponse(user, userID))
}
func toUserResponse(user *types.UserInfo, currenUserID string) *api.User { func toUserResponse(user *types.UserInfo, currenUserID string) *api.User {
autoGroups := user.AutoGroups autoGroups := user.AutoGroups
if autoGroups == nil { if autoGroups == nil {
@@ -9,6 +9,7 @@ import (
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"testing" "testing"
"time"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@@ -123,6 +124,64 @@ func initUsersTestData() *handler {
return nil return nil
}, },
GetCurrentUserInfoFunc: func(ctx context.Context, accountID, userID string) (*types.UserInfo, error) {
switch userID {
case "not-found":
return nil, status.NewUserNotFoundError("not-found")
case "not-of-account":
return nil, status.NewUserNotPartOfAccountError()
case "blocked-user":
return nil, status.NewUserBlockedError()
case "service-user":
return nil, status.NewPermissionDeniedError()
case "owner":
return &types.UserInfo{
ID: "owner",
Name: "",
Role: "owner",
Status: "active",
IsServiceUser: false,
IsBlocked: false,
NonDeletable: false,
Issued: "api",
Permissions: types.UserPermissions{
DashboardView: "full",
},
}, nil
case "regular-user":
return &types.UserInfo{
ID: "regular-user",
Name: "",
Role: "user",
Status: "active",
IsServiceUser: false,
IsBlocked: false,
NonDeletable: false,
Issued: "api",
Permissions: types.UserPermissions{
DashboardView: "limited",
},
}, nil
case "admin-user":
return &types.UserInfo{
ID: "admin-user",
Name: "",
Role: "admin",
Status: "active",
IsServiceUser: false,
IsBlocked: false,
NonDeletable: false,
LastLogin: time.Time{},
Issued: "api",
Permissions: types.UserPermissions{
DashboardView: "full",
},
}, nil
}
return nil, fmt.Errorf("user id %s not handled", userID)
},
}, },
} }
} }
@@ -481,3 +540,73 @@ func TestDeleteUser(t *testing.T) {
}) })
} }
} }
func TestCurrentUser(t *testing.T) {
tt := []struct {
name string
expectedStatus int
requestAuth nbcontext.UserAuth
}{
{
name: "without auth",
expectedStatus: http.StatusInternalServerError,
},
{
name: "user not found",
requestAuth: nbcontext.UserAuth{UserId: "not-found"},
expectedStatus: http.StatusNotFound,
},
{
name: "not of account",
requestAuth: nbcontext.UserAuth{UserId: "not-of-account"},
expectedStatus: http.StatusForbidden,
},
{
name: "blocked user",
requestAuth: nbcontext.UserAuth{UserId: "blocked-user"},
expectedStatus: http.StatusForbidden,
},
{
name: "service user",
requestAuth: nbcontext.UserAuth{UserId: "service-user"},
expectedStatus: http.StatusForbidden,
},
{
name: "owner",
requestAuth: nbcontext.UserAuth{UserId: "owner"},
expectedStatus: http.StatusOK,
},
{
name: "regular user",
requestAuth: nbcontext.UserAuth{UserId: "regular-user"},
expectedStatus: http.StatusOK,
},
{
name: "admin user",
requestAuth: nbcontext.UserAuth{UserId: "admin-user"},
expectedStatus: http.StatusOK,
},
}
userHandler := initUsersTestData()
for _, tc := range tt {
t.Run(tc.name, func(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/api/users/current", nil)
if tc.requestAuth.UserId != "" {
req = nbcontext.SetUserAuthInRequest(req, tc.requestAuth)
}
rr := httptest.NewRecorder()
userHandler.getCurrentUser(rr, req)
res := rr.Result()
defer res.Body.Close()
if status := rr.Code; status != tc.expectedStatus {
t.Fatalf("handler returned wrong status code: got %v want %v",
status, tc.expectedStatus)
}
})
}
}
@@ -1,77 +0,0 @@
package middleware
import (
"context"
"net/http"
"regexp"
log "github.com/sirupsen/logrus"
nbcontext "github.com/netbirdio/netbird/management/server/context"
"github.com/netbirdio/netbird/management/server/http/middleware/bypass"
"github.com/netbirdio/netbird/management/server/http/util"
"github.com/netbirdio/netbird/management/server/status"
"github.com/netbirdio/netbird/management/server/types"
)
// GetUser function defines a function to fetch user from Account by jwtclaims.AuthorizationClaims
type GetUser func(ctx context.Context, userAuth nbcontext.UserAuth) (*types.User, error)
// AccessControl middleware to restrict to make POST/PUT/DELETE requests by admin only
type AccessControl struct {
getUser GetUser
}
// NewAccessControl instance constructor
func NewAccessControl(getUser GetUser) *AccessControl {
return &AccessControl{
getUser: getUser,
}
}
var tokenPathRegexp = regexp.MustCompile(`^.*/api/users/.*/tokens.*$`)
// Handler method of the middleware which forbids all modify requests for non admin users
func (a *AccessControl) Handler(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if bypass.ShouldBypass(r.URL.Path, h, w, r) {
return
}
userAuth, err := nbcontext.GetUserAuthFromRequest(r)
if err != nil {
log.WithContext(r.Context()).Errorf("failed to get user auth from request: %s", err)
util.WriteError(r.Context(), status.Errorf(status.Unauthorized, "invalid user auth"), w)
}
user, err := a.getUser(r.Context(), userAuth)
if err != nil {
log.WithContext(r.Context()).Errorf("failed to get user: %s", err)
util.WriteError(r.Context(), status.Errorf(status.Unauthorized, "invalid user auth"), w)
return
}
if user.IsBlocked() {
util.WriteError(r.Context(), status.Errorf(status.PermissionDenied, "the user has no access to the API or is blocked"), w)
return
}
if !user.HasAdminPower() {
switch r.Method {
case http.MethodDelete, http.MethodPost, http.MethodPatch, http.MethodPut:
if tokenPathRegexp.MatchString(r.URL.Path) {
log.WithContext(r.Context()).Debugf("valid Path")
h.ServeHTTP(w, r)
return
}
util.WriteError(r.Context(), status.Errorf(status.PermissionDenied, "only users with admin power can perform this operation"), w)
return
}
}
h.ServeHTTP(w, r)
})
}
@@ -15,7 +15,6 @@ import (
"time" "time"
"github.com/golang-jwt/jwt" "github.com/golang-jwt/jwt"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes" "golang.zx2c4.com/wireguard/wgctrl/wgtypes"
@@ -123,9 +122,9 @@ func BuildApiBlackBoxWithDBState(t TB, sqlFile string, expectedPeerUpdate *serve
validatorMock := server.MocIntegratedValidator{} validatorMock := server.MocIntegratedValidator{}
proxyController := integrations.NewController(store) proxyController := integrations.NewController(store)
userManager := users.NewManager(store) userManager := users.NewManager(store)
permissionsManagerMock := permissions.NewManagerMock() permissionsManager := permissions.NewManager(store)
settingsManager := settings.NewManager(store, userManager, integrations.NewManager(&activity.InMemoryEventStore{}), permissionsManagerMock) settingsManager := settings.NewManager(store, userManager, integrations.NewManager(&activity.InMemoryEventStore{}), permissionsManager)
am, err := server.BuildManager(context.Background(), store, peersUpdateManager, nil, "", "", &activity.InMemoryEventStore{}, geoMock, false, validatorMock, metrics, proxyController, settingsManager, permissionsManagerMock) am, err := server.BuildManager(context.Background(), store, peersUpdateManager, nil, "", "", &activity.InMemoryEventStore{}, geoMock, false, validatorMock, metrics, proxyController, settingsManager, permissionsManager)
if err != nil { if err != nil {
t.Fatalf("Failed to create manager: %v", err) t.Fatalf("Failed to create manager: %v", err)
} }
@@ -143,9 +142,9 @@ func BuildApiBlackBoxWithDBState(t TB, sqlFile string, expectedPeerUpdate *serve
resourcesManagerMock := resources.NewManagerMock() resourcesManagerMock := resources.NewManagerMock()
routersManagerMock := routers.NewManagerMock() routersManagerMock := routers.NewManagerMock()
groupsManagerMock := groups.NewManagerMock() groupsManagerMock := groups.NewManagerMock()
peersManager := peers.NewManager(store, permissionsManagerMock) peersManager := peers.NewManager(store, permissionsManager)
apiHandler, err := nbhttp.NewAPIHandler(context.Background(), am, networksManagerMock, resourcesManagerMock, routersManagerMock, groupsManagerMock, geoMock, authManagerMock, metrics, validatorMock, proxyController, permissionsManagerMock, peersManager, settingsManager) apiHandler, err := nbhttp.NewAPIHandler(context.Background(), am, networksManagerMock, resourcesManagerMock, routersManagerMock, groupsManagerMock, geoMock, authManagerMock, metrics, validatorMock, proxyController, permissionsManager, peersManager, settingsManager)
if err != nil { if err != nil {
t.Fatalf("Failed to create API handler: %v", err) t.Fatalf("Failed to create API handler: %v", err)
} }
+3 -3
View File
@@ -432,8 +432,6 @@ func startManagementForTest(t *testing.T, testFile string, config *types.Config)
metrics, err := telemetry.NewDefaultAppMetrics(context.Background()) metrics, err := telemetry.NewDefaultAppMetrics(context.Background())
require.NoError(t, err) require.NoError(t, err)
permissionsManagerMock := permissions.NewManagerMock()
ctrl := gomock.NewController(t) ctrl := gomock.NewController(t)
t.Cleanup(ctrl.Finish) t.Cleanup(ctrl.Finish)
settingsMockManager := settings.NewMockManager(ctrl) settingsMockManager := settings.NewMockManager(ctrl)
@@ -443,8 +441,10 @@ func startManagementForTest(t *testing.T, testFile string, config *types.Config)
AnyTimes(). AnyTimes().
Return(&types.Settings{}, nil) Return(&types.Settings{}, nil)
permissionsManager := permissions.NewManager(store)
accountManager, err := BuildManager(ctx, store, peersUpdateManager, nil, "", "netbird.selfhosted", accountManager, err := BuildManager(ctx, store, peersUpdateManager, nil, "", "netbird.selfhosted",
eventStore, nil, false, MocIntegratedValidator{}, metrics, port_forwarding.NewControllerMock(), settingsMockManager, permissionsManagerMock) eventStore, nil, false, MocIntegratedValidator{}, metrics, port_forwarding.NewControllerMock(), settingsMockManager, permissionsManager)
if err != nil { if err != nil {
cleanup() cleanup()
+2 -2
View File
@@ -195,7 +195,7 @@ func startServer(
Return(&types.Settings{}, nil). Return(&types.Settings{}, nil).
AnyTimes() AnyTimes()
permissionsManagerMock := permissions.NewManagerMock() permissionsManager := permissions.NewManager(str)
accountManager, err := server.BuildManager( accountManager, err := server.BuildManager(
context.Background(), context.Background(),
str, str,
@@ -210,7 +210,7 @@ func startServer(
metrics, metrics,
port_forwarding.NewControllerMock(), port_forwarding.NewControllerMock(),
settingsMockManager, settingsMockManager,
permissionsManagerMock, permissionsManager,
) )
if err != nil { if err != nil {
t.Fatalf("failed creating an account manager: %v", err) t.Fatalf("failed creating an account manager: %v", err)
+20 -12
View File
@@ -44,8 +44,8 @@ type MockAccountManager struct {
GetGroupFunc func(ctx context.Context, accountID, groupID, userID string) (*types.Group, error) GetGroupFunc func(ctx context.Context, accountID, groupID, userID string) (*types.Group, error)
GetAllGroupsFunc func(ctx context.Context, accountID, userID string) ([]*types.Group, error) GetAllGroupsFunc func(ctx context.Context, accountID, userID string) ([]*types.Group, error)
GetGroupByNameFunc func(ctx context.Context, accountID, groupName string) (*types.Group, error) GetGroupByNameFunc func(ctx context.Context, accountID, groupName string) (*types.Group, error)
SaveGroupFunc func(ctx context.Context, accountID, userID string, group *types.Group) error SaveGroupFunc func(ctx context.Context, accountID, userID string, group *types.Group, create bool) error
SaveGroupsFunc func(ctx context.Context, accountID, userID string, groups []*types.Group) error SaveGroupsFunc func(ctx context.Context, accountID, userID string, groups []*types.Group, create bool) error
DeleteGroupFunc func(ctx context.Context, accountID, userId, groupID string) error DeleteGroupFunc func(ctx context.Context, accountID, userId, groupID string) error
DeleteGroupsFunc func(ctx context.Context, accountId, userId string, groupIDs []string) error DeleteGroupsFunc func(ctx context.Context, accountId, userId string, groupIDs []string) error
GroupAddPeerFunc func(ctx context.Context, accountID, groupID, peerID string) error GroupAddPeerFunc func(ctx context.Context, accountID, groupID, peerID string) error
@@ -53,7 +53,7 @@ type MockAccountManager struct {
GetPeerGroupsFunc func(ctx context.Context, accountID, peerID string) ([]*types.Group, error) GetPeerGroupsFunc func(ctx context.Context, accountID, peerID string) ([]*types.Group, error)
DeleteRuleFunc func(ctx context.Context, accountID, ruleID, userID string) error DeleteRuleFunc func(ctx context.Context, accountID, ruleID, userID string) error
GetPolicyFunc func(ctx context.Context, accountID, policyID, userID string) (*types.Policy, error) GetPolicyFunc func(ctx context.Context, accountID, policyID, userID string) (*types.Policy, error)
SavePolicyFunc func(ctx context.Context, accountID, userID string, policy *types.Policy) (*types.Policy, error) SavePolicyFunc func(ctx context.Context, accountID, userID string, policy *types.Policy, create bool) (*types.Policy, error)
DeletePolicyFunc func(ctx context.Context, accountID, policyID, userID string) error DeletePolicyFunc func(ctx context.Context, accountID, policyID, userID string) error
ListPoliciesFunc func(ctx context.Context, accountID, userID string) ([]*types.Policy, error) ListPoliciesFunc func(ctx context.Context, accountID, userID string) ([]*types.Policy, error)
GetUsersFromAccountFunc func(ctx context.Context, accountID, userID string) (map[string]*types.UserInfo, error) GetUsersFromAccountFunc func(ctx context.Context, accountID, userID string) (map[string]*types.UserInfo, error)
@@ -97,7 +97,7 @@ type MockAccountManager struct {
HasConnectedChannelFunc func(peerID string) bool HasConnectedChannelFunc func(peerID string) bool
GetExternalCacheManagerFunc func() account.ExternalCacheManager GetExternalCacheManagerFunc func() account.ExternalCacheManager
GetPostureChecksFunc func(ctx context.Context, accountID, postureChecksID, userID string) (*posture.Checks, error) GetPostureChecksFunc func(ctx context.Context, accountID, postureChecksID, userID string) (*posture.Checks, error)
SavePostureChecksFunc func(ctx context.Context, accountID, userID string, postureChecks *posture.Checks) (*posture.Checks, error) SavePostureChecksFunc func(ctx context.Context, accountID, userID string, postureChecks *posture.Checks, create bool) (*posture.Checks, error)
DeletePostureChecksFunc func(ctx context.Context, accountID, postureChecksID, userID string) error DeletePostureChecksFunc func(ctx context.Context, accountID, postureChecksID, userID string) error
ListPostureChecksFunc func(ctx context.Context, accountID, userID string) ([]*posture.Checks, error) ListPostureChecksFunc func(ctx context.Context, accountID, userID string) ([]*posture.Checks, error)
GetIdpManagerFunc func() idp.Manager GetIdpManagerFunc func() idp.Manager
@@ -115,6 +115,7 @@ type MockAccountManager struct {
CreateAccountByPrivateDomainFunc func(ctx context.Context, initiatorId, domain string) (*types.Account, error) CreateAccountByPrivateDomainFunc func(ctx context.Context, initiatorId, domain string) (*types.Account, error)
UpdateToPrimaryAccountFunc func(ctx context.Context, accountId string) (*types.Account, error) UpdateToPrimaryAccountFunc func(ctx context.Context, accountId string) (*types.Account, error)
GetOwnerInfoFunc func(ctx context.Context, accountID string) (*types.UserInfo, error) GetOwnerInfoFunc func(ctx context.Context, accountID string) (*types.UserInfo, error)
GetCurrentUserInfoFunc func(ctx context.Context, accountID, userID string) (*types.UserInfo, error)
} }
func (am *MockAccountManager) UpdateAccountPeers(ctx context.Context, accountID string) { func (am *MockAccountManager) UpdateAccountPeers(ctx context.Context, accountID string) {
@@ -322,17 +323,17 @@ func (am *MockAccountManager) GetGroupByName(ctx context.Context, accountID, gro
} }
// SaveGroup mock implementation of SaveGroup from server.AccountManager interface // SaveGroup mock implementation of SaveGroup from server.AccountManager interface
func (am *MockAccountManager) SaveGroup(ctx context.Context, accountID, userID string, group *types.Group) error { func (am *MockAccountManager) SaveGroup(ctx context.Context, accountID, userID string, group *types.Group, create bool) error {
if am.SaveGroupFunc != nil { if am.SaveGroupFunc != nil {
return am.SaveGroupFunc(ctx, accountID, userID, group) return am.SaveGroupFunc(ctx, accountID, userID, group, create)
} }
return status.Errorf(codes.Unimplemented, "method SaveGroup is not implemented") return status.Errorf(codes.Unimplemented, "method SaveGroup is not implemented")
} }
// SaveGroups mock implementation of SaveGroups from server.AccountManager interface // SaveGroups mock implementation of SaveGroups from server.AccountManager interface
func (am *MockAccountManager) SaveGroups(ctx context.Context, accountID, userID string, groups []*types.Group) error { func (am *MockAccountManager) SaveGroups(ctx context.Context, accountID, userID string, groups []*types.Group, create bool) error {
if am.SaveGroupsFunc != nil { if am.SaveGroupsFunc != nil {
return am.SaveGroupsFunc(ctx, accountID, userID, groups) return am.SaveGroupsFunc(ctx, accountID, userID, groups, create)
} }
return status.Errorf(codes.Unimplemented, "method SaveGroups is not implemented") return status.Errorf(codes.Unimplemented, "method SaveGroups is not implemented")
} }
@@ -386,9 +387,9 @@ func (am *MockAccountManager) GetPolicy(ctx context.Context, accountID, policyID
} }
// SavePolicy mock implementation of SavePolicy from server.AccountManager interface // SavePolicy mock implementation of SavePolicy from server.AccountManager interface
func (am *MockAccountManager) SavePolicy(ctx context.Context, accountID, userID string, policy *types.Policy) (*types.Policy, error) { func (am *MockAccountManager) SavePolicy(ctx context.Context, accountID, userID string, policy *types.Policy, create bool) (*types.Policy, error) {
if am.SavePolicyFunc != nil { if am.SavePolicyFunc != nil {
return am.SavePolicyFunc(ctx, accountID, userID, policy) return am.SavePolicyFunc(ctx, accountID, userID, policy, create)
} }
return nil, status.Errorf(codes.Unimplemented, "method SavePolicy is not implemented") return nil, status.Errorf(codes.Unimplemented, "method SavePolicy is not implemented")
} }
@@ -722,9 +723,9 @@ func (am *MockAccountManager) GetPostureChecks(ctx context.Context, accountID, p
} }
// SavePostureChecks mocks SavePostureChecks of the AccountManager interface // SavePostureChecks mocks SavePostureChecks of the AccountManager interface
func (am *MockAccountManager) SavePostureChecks(ctx context.Context, accountID, userID string, postureChecks *posture.Checks) (*posture.Checks, error) { func (am *MockAccountManager) SavePostureChecks(ctx context.Context, accountID, userID string, postureChecks *posture.Checks, create bool) (*posture.Checks, error) {
if am.SavePostureChecksFunc != nil { if am.SavePostureChecksFunc != nil {
return am.SavePostureChecksFunc(ctx, accountID, userID, postureChecks) return am.SavePostureChecksFunc(ctx, accountID, userID, postureChecks, create)
} }
return nil, status.Errorf(codes.Unimplemented, "method SavePostureChecks is not implemented") return nil, status.Errorf(codes.Unimplemented, "method SavePostureChecks is not implemented")
} }
@@ -871,3 +872,10 @@ func (am *MockAccountManager) GetOwnerInfo(ctx context.Context, accountId string
} }
return nil, status.Errorf(codes.Unimplemented, "method GetOwnerInfo is not implemented") return nil, status.Errorf(codes.Unimplemented, "method GetOwnerInfo is not implemented")
} }
func (am *MockAccountManager) GetCurrentUserInfo(ctx context.Context, accountID, userID string) (*types.UserInfo, error) {
if am.GetCurrentUserInfoFunc != nil {
return am.GetCurrentUserInfoFunc(ctx, accountID, userID)
}
return nil, status.Errorf(codes.Unimplemented, "method GetCurrentUserInfo is not implemented")
}
+22 -33
View File
@@ -11,6 +11,8 @@ import (
nbdns "github.com/netbirdio/netbird/dns" nbdns "github.com/netbirdio/netbird/dns"
"github.com/netbirdio/netbird/management/server/activity" "github.com/netbirdio/netbird/management/server/activity"
"github.com/netbirdio/netbird/management/server/permissions/modules"
"github.com/netbirdio/netbird/management/server/permissions/operations"
"github.com/netbirdio/netbird/management/server/status" "github.com/netbirdio/netbird/management/server/status"
"github.com/netbirdio/netbird/management/server/store" "github.com/netbirdio/netbird/management/server/store"
"github.com/netbirdio/netbird/management/server/types" "github.com/netbirdio/netbird/management/server/types"
@@ -20,17 +22,12 @@ const domainPattern = `^(?i)[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,}$`
// GetNameServerGroup gets a nameserver group object from account and nameserver group IDs // GetNameServerGroup gets a nameserver group object from account and nameserver group IDs
func (am *DefaultAccountManager) GetNameServerGroup(ctx context.Context, accountID, userID, nsGroupID string) (*nbdns.NameServerGroup, error) { func (am *DefaultAccountManager) GetNameServerGroup(ctx context.Context, accountID, userID, nsGroupID string) (*nbdns.NameServerGroup, error) {
user, err := am.Store.GetUserByUserID(ctx, store.LockingStrengthShare, userID) allowed, err := am.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Nameservers, operations.Read)
if err != nil { if err != nil {
return nil, err return nil, status.NewPermissionValidationError(err)
} }
if !allowed {
if err := am.permissionsManager.ValidateAccountAccess(ctx, accountID, user, false); err != nil { return nil, status.NewPermissionDeniedError()
return nil, err
}
if user.IsRegularUser() {
return nil, status.NewAdminPermissionError()
} }
return am.Store.GetNameServerGroupByID(ctx, store.LockingStrengthShare, accountID, nsGroupID) return am.Store.GetNameServerGroupByID(ctx, store.LockingStrengthShare, accountID, nsGroupID)
@@ -41,13 +38,12 @@ func (am *DefaultAccountManager) CreateNameServerGroup(ctx context.Context, acco
unlock := am.Store.AcquireWriteLockByUID(ctx, accountID) unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
defer unlock() defer unlock()
user, err := am.Store.GetUserByUserID(ctx, store.LockingStrengthShare, userID) allowed, err := am.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Nameservers, operations.Create)
if err != nil { if err != nil {
return nil, err return nil, status.NewPermissionValidationError(err)
} }
if !allowed {
if err := am.permissionsManager.ValidateAccountAccess(ctx, accountID, user, false); err != nil { return nil, status.NewPermissionDeniedError()
return nil, err
} }
newNSGroup := &nbdns.NameServerGroup{ newNSGroup := &nbdns.NameServerGroup{
@@ -103,13 +99,12 @@ func (am *DefaultAccountManager) SaveNameServerGroup(ctx context.Context, accoun
return status.Errorf(status.InvalidArgument, "nameserver group provided is nil") return status.Errorf(status.InvalidArgument, "nameserver group provided is nil")
} }
user, err := am.Store.GetUserByUserID(ctx, store.LockingStrengthShare, userID) allowed, err := am.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Nameservers, operations.Update)
if err != nil { if err != nil {
return err return status.NewPermissionValidationError(err)
} }
if !allowed {
if err := am.permissionsManager.ValidateAccountAccess(ctx, accountID, user, false); err != nil { return status.NewPermissionDeniedError()
return err
} }
var updateAccountPeers bool var updateAccountPeers bool
@@ -154,13 +149,12 @@ func (am *DefaultAccountManager) DeleteNameServerGroup(ctx context.Context, acco
unlock := am.Store.AcquireWriteLockByUID(ctx, accountID) unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
defer unlock() defer unlock()
user, err := am.Store.GetUserByUserID(ctx, store.LockingStrengthShare, userID) allowed, err := am.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Nameservers, operations.Delete)
if err != nil { if err != nil {
return err return status.NewPermissionValidationError(err)
} }
if !allowed {
if err := am.permissionsManager.ValidateAccountAccess(ctx, accountID, user, false); err != nil { return status.NewPermissionDeniedError()
return err
} }
var nsGroup *nbdns.NameServerGroup var nsGroup *nbdns.NameServerGroup
@@ -198,17 +192,12 @@ func (am *DefaultAccountManager) DeleteNameServerGroup(ctx context.Context, acco
// ListNameServerGroups returns a list of nameserver groups from account // ListNameServerGroups returns a list of nameserver groups from account
func (am *DefaultAccountManager) ListNameServerGroups(ctx context.Context, accountID string, userID string) ([]*nbdns.NameServerGroup, error) { func (am *DefaultAccountManager) ListNameServerGroups(ctx context.Context, accountID string, userID string) ([]*nbdns.NameServerGroup, error) {
user, err := am.Store.GetUserByUserID(ctx, store.LockingStrengthShare, userID) allowed, err := am.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Nameservers, operations.Read)
if err != nil { if err != nil {
return nil, err return nil, status.NewPermissionValidationError(err)
} }
if !allowed {
if err := am.permissionsManager.ValidateAccountAccess(ctx, accountID, user, false); err != nil { return nil, status.NewPermissionDeniedError()
return nil, err
}
if user.IsRegularUser() {
return nil, status.NewAdminPermissionError()
} }
return am.Store.GetAccountNameServerGroups(ctx, store.LockingStrengthShare, accountID) return am.Store.GetAccountNameServerGroups(ctx, store.LockingStrengthShare, accountID)
+3 -4
View File
@@ -775,12 +775,11 @@ func createNSManager(t *testing.T) (*DefaultAccountManager, error) {
metrics, err := telemetry.NewDefaultAppMetrics(context.Background()) metrics, err := telemetry.NewDefaultAppMetrics(context.Background())
require.NoError(t, err) require.NoError(t, err)
permissionsManagerMock := permissions.NewManagerMock()
ctrl := gomock.NewController(t) ctrl := gomock.NewController(t)
t.Cleanup(ctrl.Finish) t.Cleanup(ctrl.Finish)
settingsMockManager := settings.NewMockManager(ctrl) settingsMockManager := settings.NewMockManager(ctrl)
permissionsManager := permissions.NewManager(store)
return BuildManager(context.Background(), store, NewPeersUpdateManager(nil), nil, "", "netbird.selfhosted", eventStore, nil, false, MocIntegratedValidator{}, metrics, port_forwarding.NewControllerMock(), settingsMockManager, permissionsManagerMock) return BuildManager(context.Background(), store, NewPeersUpdateManager(nil), nil, "", "netbird.selfhosted", eventStore, nil, false, MocIntegratedValidator{}, metrics, port_forwarding.NewControllerMock(), settingsMockManager, permissionsManager)
} }
func createNSStore(t *testing.T) (store.Store, error) { func createNSStore(t *testing.T) (store.Store, error) {
@@ -966,7 +965,7 @@ func TestNameServerAccountPeersUpdate(t *testing.T) {
Name: "GroupB", Name: "GroupB",
Peers: []string{peer1.ID, peer2.ID, peer3.ID}, Peers: []string{peer1.ID, peer2.ID, peer3.ID},
}, },
}) }, true)
assert.NoError(t, err) assert.NoError(t, err)
updMsg := manager.peersUpdateManager.CreateChannel(context.Background(), peer1.ID) updMsg := manager.peersUpdateManager.CreateChannel(context.Background(), peer1.ID)
+7 -5
View File
@@ -12,6 +12,8 @@ import (
"github.com/netbirdio/netbird/management/server/networks/routers" "github.com/netbirdio/netbird/management/server/networks/routers"
"github.com/netbirdio/netbird/management/server/networks/types" "github.com/netbirdio/netbird/management/server/networks/types"
"github.com/netbirdio/netbird/management/server/permissions" "github.com/netbirdio/netbird/management/server/permissions"
"github.com/netbirdio/netbird/management/server/permissions/modules"
"github.com/netbirdio/netbird/management/server/permissions/operations"
"github.com/netbirdio/netbird/management/server/status" "github.com/netbirdio/netbird/management/server/status"
"github.com/netbirdio/netbird/management/server/store" "github.com/netbirdio/netbird/management/server/store"
) )
@@ -46,7 +48,7 @@ func NewManager(store store.Store, permissionsManager permissions.Manager, resou
} }
func (m *managerImpl) GetAllNetworks(ctx context.Context, accountID, userID string) ([]*types.Network, error) { func (m *managerImpl) GetAllNetworks(ctx context.Context, accountID, userID string) ([]*types.Network, error) {
ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, permissions.Networks, permissions.Read) ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Networks, operations.Read)
if err != nil { if err != nil {
return nil, status.NewPermissionValidationError(err) return nil, status.NewPermissionValidationError(err)
} }
@@ -58,7 +60,7 @@ func (m *managerImpl) GetAllNetworks(ctx context.Context, accountID, userID stri
} }
func (m *managerImpl) CreateNetwork(ctx context.Context, userID string, network *types.Network) (*types.Network, error) { func (m *managerImpl) CreateNetwork(ctx context.Context, userID string, network *types.Network) (*types.Network, error) {
ok, err := m.permissionsManager.ValidateUserPermissions(ctx, network.AccountID, userID, permissions.Networks, permissions.Write) ok, err := m.permissionsManager.ValidateUserPermissions(ctx, network.AccountID, userID, modules.Networks, operations.Create)
if err != nil { if err != nil {
return nil, status.NewPermissionValidationError(err) return nil, status.NewPermissionValidationError(err)
} }
@@ -82,7 +84,7 @@ func (m *managerImpl) CreateNetwork(ctx context.Context, userID string, network
} }
func (m *managerImpl) GetNetwork(ctx context.Context, accountID, userID, networkID string) (*types.Network, error) { func (m *managerImpl) GetNetwork(ctx context.Context, accountID, userID, networkID string) (*types.Network, error) {
ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, permissions.Networks, permissions.Read) ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Networks, operations.Read)
if err != nil { if err != nil {
return nil, status.NewPermissionValidationError(err) return nil, status.NewPermissionValidationError(err)
} }
@@ -94,7 +96,7 @@ func (m *managerImpl) GetNetwork(ctx context.Context, accountID, userID, network
} }
func (m *managerImpl) UpdateNetwork(ctx context.Context, userID string, network *types.Network) (*types.Network, error) { func (m *managerImpl) UpdateNetwork(ctx context.Context, userID string, network *types.Network) (*types.Network, error) {
ok, err := m.permissionsManager.ValidateUserPermissions(ctx, network.AccountID, userID, permissions.Networks, permissions.Write) ok, err := m.permissionsManager.ValidateUserPermissions(ctx, network.AccountID, userID, modules.Networks, operations.Update)
if err != nil { if err != nil {
return nil, status.NewPermissionValidationError(err) return nil, status.NewPermissionValidationError(err)
} }
@@ -116,7 +118,7 @@ func (m *managerImpl) UpdateNetwork(ctx context.Context, userID string, network
} }
func (m *managerImpl) DeleteNetwork(ctx context.Context, accountID, userID, networkID string) error { func (m *managerImpl) DeleteNetwork(ctx context.Context, accountID, userID, networkID string) error {
ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, permissions.Networks, permissions.Write) ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Networks, operations.Delete)
if err != nil { if err != nil {
return status.NewPermissionValidationError(err) return status.NewPermissionValidationError(err)
} }
+20 -20
View File
@@ -18,7 +18,7 @@ import (
func Test_GetAllNetworksReturnsNetworks(t *testing.T) { func Test_GetAllNetworksReturnsNetworks(t *testing.T) {
ctx := context.Background() ctx := context.Background()
accountID := "testAccountId" accountID := "testAccountId"
userID := "allowedUser" userID := "testAdminId"
s, cleanUp, err := store.NewTestStoreFromSQL(context.Background(), "../testdata/networks.sql", t.TempDir()) s, cleanUp, err := store.NewTestStoreFromSQL(context.Background(), "../testdata/networks.sql", t.TempDir())
if err != nil { if err != nil {
@@ -26,7 +26,7 @@ func Test_GetAllNetworksReturnsNetworks(t *testing.T) {
} }
t.Cleanup(cleanUp) t.Cleanup(cleanUp)
am := mock_server.MockAccountManager{} am := mock_server.MockAccountManager{}
permissionsManager := permissions.NewManagerMock() permissionsManager := permissions.NewManager(s)
groupsManager := groups.NewManagerMock() groupsManager := groups.NewManagerMock()
routerManager := routers.NewManagerMock() routerManager := routers.NewManagerMock()
resourcesManager := resources.NewManager(s, permissionsManager, groupsManager, &am) resourcesManager := resources.NewManager(s, permissionsManager, groupsManager, &am)
@@ -41,7 +41,7 @@ func Test_GetAllNetworksReturnsNetworks(t *testing.T) {
func Test_GetAllNetworksReturnsPermissionDenied(t *testing.T) { func Test_GetAllNetworksReturnsPermissionDenied(t *testing.T) {
ctx := context.Background() ctx := context.Background()
accountID := "testAccountId" accountID := "testAccountId"
userID := "invalidUser" userID := "testUserId"
s, cleanUp, err := store.NewTestStoreFromSQL(context.Background(), "../testdata/networks.sql", t.TempDir()) s, cleanUp, err := store.NewTestStoreFromSQL(context.Background(), "../testdata/networks.sql", t.TempDir())
if err != nil { if err != nil {
@@ -49,7 +49,7 @@ func Test_GetAllNetworksReturnsPermissionDenied(t *testing.T) {
} }
t.Cleanup(cleanUp) t.Cleanup(cleanUp)
am := mock_server.MockAccountManager{} am := mock_server.MockAccountManager{}
permissionsManager := permissions.NewManagerMock() permissionsManager := permissions.NewManager(s)
groupsManager := groups.NewManagerMock() groupsManager := groups.NewManagerMock()
routerManager := routers.NewManagerMock() routerManager := routers.NewManagerMock()
resourcesManager := resources.NewManager(s, permissionsManager, groupsManager, &am) resourcesManager := resources.NewManager(s, permissionsManager, groupsManager, &am)
@@ -63,7 +63,7 @@ func Test_GetAllNetworksReturnsPermissionDenied(t *testing.T) {
func Test_GetNetworkReturnsNetwork(t *testing.T) { func Test_GetNetworkReturnsNetwork(t *testing.T) {
ctx := context.Background() ctx := context.Background()
accountID := "testAccountId" accountID := "testAccountId"
userID := "allowedUser" userID := "testAdminId"
networkID := "testNetworkId" networkID := "testNetworkId"
s, cleanUp, err := store.NewTestStoreFromSQL(context.Background(), "../testdata/networks.sql", t.TempDir()) s, cleanUp, err := store.NewTestStoreFromSQL(context.Background(), "../testdata/networks.sql", t.TempDir())
@@ -72,7 +72,7 @@ func Test_GetNetworkReturnsNetwork(t *testing.T) {
} }
t.Cleanup(cleanUp) t.Cleanup(cleanUp)
am := mock_server.MockAccountManager{} am := mock_server.MockAccountManager{}
permissionsManager := permissions.NewManagerMock() permissionsManager := permissions.NewManager(s)
groupsManager := groups.NewManagerMock() groupsManager := groups.NewManagerMock()
routerManager := routers.NewManagerMock() routerManager := routers.NewManagerMock()
resourcesManager := resources.NewManager(s, permissionsManager, groupsManager, &am) resourcesManager := resources.NewManager(s, permissionsManager, groupsManager, &am)
@@ -86,7 +86,7 @@ func Test_GetNetworkReturnsNetwork(t *testing.T) {
func Test_GetNetworkReturnsPermissionDenied(t *testing.T) { func Test_GetNetworkReturnsPermissionDenied(t *testing.T) {
ctx := context.Background() ctx := context.Background()
accountID := "testAccountId" accountID := "testAccountId"
userID := "invalidUser" userID := "testUserId"
networkID := "testNetworkId" networkID := "testNetworkId"
s, cleanUp, err := store.NewTestStoreFromSQL(context.Background(), "../testdata/networks.sql", t.TempDir()) s, cleanUp, err := store.NewTestStoreFromSQL(context.Background(), "../testdata/networks.sql", t.TempDir())
@@ -95,7 +95,7 @@ func Test_GetNetworkReturnsPermissionDenied(t *testing.T) {
} }
t.Cleanup(cleanUp) t.Cleanup(cleanUp)
am := mock_server.MockAccountManager{} am := mock_server.MockAccountManager{}
permissionsManager := permissions.NewManagerMock() permissionsManager := permissions.NewManager(s)
groupsManager := groups.NewManagerMock() groupsManager := groups.NewManagerMock()
routerManager := routers.NewManagerMock() routerManager := routers.NewManagerMock()
resourcesManager := resources.NewManager(s, permissionsManager, groupsManager, &am) resourcesManager := resources.NewManager(s, permissionsManager, groupsManager, &am)
@@ -108,7 +108,7 @@ func Test_GetNetworkReturnsPermissionDenied(t *testing.T) {
func Test_CreateNetworkSuccessfully(t *testing.T) { func Test_CreateNetworkSuccessfully(t *testing.T) {
ctx := context.Background() ctx := context.Background()
userID := "allowedUser" userID := "testAdminId"
network := &types.Network{ network := &types.Network{
AccountID: "testAccountId", AccountID: "testAccountId",
Name: "new-network", Name: "new-network",
@@ -120,7 +120,7 @@ func Test_CreateNetworkSuccessfully(t *testing.T) {
} }
t.Cleanup(cleanUp) t.Cleanup(cleanUp)
am := mock_server.MockAccountManager{} am := mock_server.MockAccountManager{}
permissionsManager := permissions.NewManagerMock() permissionsManager := permissions.NewManager(s)
groupsManager := groups.NewManagerMock() groupsManager := groups.NewManagerMock()
routerManager := routers.NewManagerMock() routerManager := routers.NewManagerMock()
resourcesManager := resources.NewManager(s, permissionsManager, groupsManager, &am) resourcesManager := resources.NewManager(s, permissionsManager, groupsManager, &am)
@@ -133,7 +133,7 @@ func Test_CreateNetworkSuccessfully(t *testing.T) {
func Test_CreateNetworkFailsWithPermissionDenied(t *testing.T) { func Test_CreateNetworkFailsWithPermissionDenied(t *testing.T) {
ctx := context.Background() ctx := context.Background()
userID := "invalidUser" userID := "testUserId"
network := &types.Network{ network := &types.Network{
AccountID: "testAccountId", AccountID: "testAccountId",
Name: "new-network", Name: "new-network",
@@ -145,7 +145,7 @@ func Test_CreateNetworkFailsWithPermissionDenied(t *testing.T) {
} }
t.Cleanup(cleanUp) t.Cleanup(cleanUp)
am := mock_server.MockAccountManager{} am := mock_server.MockAccountManager{}
permissionsManager := permissions.NewManagerMock() permissionsManager := permissions.NewManager(s)
groupsManager := groups.NewManagerMock() groupsManager := groups.NewManagerMock()
routerManager := routers.NewManagerMock() routerManager := routers.NewManagerMock()
resourcesManager := resources.NewManager(s, permissionsManager, groupsManager, &am) resourcesManager := resources.NewManager(s, permissionsManager, groupsManager, &am)
@@ -159,7 +159,7 @@ func Test_CreateNetworkFailsWithPermissionDenied(t *testing.T) {
func Test_DeleteNetworkSuccessfully(t *testing.T) { func Test_DeleteNetworkSuccessfully(t *testing.T) {
ctx := context.Background() ctx := context.Background()
accountID := "testAccountId" accountID := "testAccountId"
userID := "allowedUser" userID := "testAdminId"
networkID := "testNetworkId" networkID := "testNetworkId"
s, cleanUp, err := store.NewTestStoreFromSQL(context.Background(), "../testdata/networks.sql", t.TempDir()) s, cleanUp, err := store.NewTestStoreFromSQL(context.Background(), "../testdata/networks.sql", t.TempDir())
@@ -168,7 +168,7 @@ func Test_DeleteNetworkSuccessfully(t *testing.T) {
} }
t.Cleanup(cleanUp) t.Cleanup(cleanUp)
am := mock_server.MockAccountManager{} am := mock_server.MockAccountManager{}
permissionsManager := permissions.NewManagerMock() permissionsManager := permissions.NewManager(s)
groupsManager := groups.NewManagerMock() groupsManager := groups.NewManagerMock()
routerManager := routers.NewManagerMock() routerManager := routers.NewManagerMock()
resourcesManager := resources.NewManager(s, permissionsManager, groupsManager, &am) resourcesManager := resources.NewManager(s, permissionsManager, groupsManager, &am)
@@ -181,7 +181,7 @@ func Test_DeleteNetworkSuccessfully(t *testing.T) {
func Test_DeleteNetworkFailsWithPermissionDenied(t *testing.T) { func Test_DeleteNetworkFailsWithPermissionDenied(t *testing.T) {
ctx := context.Background() ctx := context.Background()
accountID := "testAccountId" accountID := "testAccountId"
userID := "invalidUser" userID := "testUserId"
networkID := "testNetworkId" networkID := "testNetworkId"
s, cleanUp, err := store.NewTestStoreFromSQL(context.Background(), "../testdata/networks.sql", t.TempDir()) s, cleanUp, err := store.NewTestStoreFromSQL(context.Background(), "../testdata/networks.sql", t.TempDir())
@@ -190,7 +190,7 @@ func Test_DeleteNetworkFailsWithPermissionDenied(t *testing.T) {
} }
t.Cleanup(cleanUp) t.Cleanup(cleanUp)
am := mock_server.MockAccountManager{} am := mock_server.MockAccountManager{}
permissionsManager := permissions.NewManagerMock() permissionsManager := permissions.NewManager(s)
groupsManager := groups.NewManagerMock() groupsManager := groups.NewManagerMock()
routerManager := routers.NewManagerMock() routerManager := routers.NewManagerMock()
resourcesManager := resources.NewManager(s, permissionsManager, groupsManager, &am) resourcesManager := resources.NewManager(s, permissionsManager, groupsManager, &am)
@@ -202,7 +202,7 @@ func Test_DeleteNetworkFailsWithPermissionDenied(t *testing.T) {
func Test_UpdateNetworkSuccessfully(t *testing.T) { func Test_UpdateNetworkSuccessfully(t *testing.T) {
ctx := context.Background() ctx := context.Background()
userID := "allowedUser" userID := "testAdminId"
network := &types.Network{ network := &types.Network{
AccountID: "testAccountId", AccountID: "testAccountId",
ID: "testNetworkId", ID: "testNetworkId",
@@ -215,7 +215,7 @@ func Test_UpdateNetworkSuccessfully(t *testing.T) {
} }
t.Cleanup(cleanUp) t.Cleanup(cleanUp)
am := mock_server.MockAccountManager{} am := mock_server.MockAccountManager{}
permissionsManager := permissions.NewManagerMock() permissionsManager := permissions.NewManager(s)
groupsManager := groups.NewManagerMock() groupsManager := groups.NewManagerMock()
routerManager := routers.NewManagerMock() routerManager := routers.NewManagerMock()
resourcesManager := resources.NewManager(s, permissionsManager, groupsManager, &am) resourcesManager := resources.NewManager(s, permissionsManager, groupsManager, &am)
@@ -228,7 +228,7 @@ func Test_UpdateNetworkSuccessfully(t *testing.T) {
func Test_UpdateNetworkFailsWithPermissionDenied(t *testing.T) { func Test_UpdateNetworkFailsWithPermissionDenied(t *testing.T) {
ctx := context.Background() ctx := context.Background()
userID := "invalidUser" userID := "testUserId"
network := &types.Network{ network := &types.Network{
AccountID: "testAccountId", AccountID: "testAccountId",
ID: "testNetworkId", ID: "testNetworkId",
@@ -242,7 +242,7 @@ func Test_UpdateNetworkFailsWithPermissionDenied(t *testing.T) {
t.Cleanup(cleanUp) t.Cleanup(cleanUp)
am := mock_server.MockAccountManager{} am := mock_server.MockAccountManager{}
permissionsManager := permissions.NewManagerMock() permissionsManager := permissions.NewManager(s)
groupsManager := groups.NewManagerMock() groupsManager := groups.NewManagerMock()
routerManager := routers.NewManagerMock() routerManager := routers.NewManagerMock()
resourcesManager := resources.NewManager(s, permissionsManager, groupsManager, &am) resourcesManager := resources.NewManager(s, permissionsManager, groupsManager, &am)
@@ -10,6 +10,8 @@ import (
"github.com/netbirdio/netbird/management/server/groups" "github.com/netbirdio/netbird/management/server/groups"
"github.com/netbirdio/netbird/management/server/networks/resources/types" "github.com/netbirdio/netbird/management/server/networks/resources/types"
"github.com/netbirdio/netbird/management/server/permissions" "github.com/netbirdio/netbird/management/server/permissions"
"github.com/netbirdio/netbird/management/server/permissions/modules"
"github.com/netbirdio/netbird/management/server/permissions/operations"
"github.com/netbirdio/netbird/management/server/status" "github.com/netbirdio/netbird/management/server/status"
"github.com/netbirdio/netbird/management/server/store" "github.com/netbirdio/netbird/management/server/store"
nbtypes "github.com/netbirdio/netbird/management/server/types" nbtypes "github.com/netbirdio/netbird/management/server/types"
@@ -47,7 +49,7 @@ func NewManager(store store.Store, permissionsManager permissions.Manager, group
} }
func (m *managerImpl) GetAllResourcesInNetwork(ctx context.Context, accountID, userID, networkID string) ([]*types.NetworkResource, error) { func (m *managerImpl) GetAllResourcesInNetwork(ctx context.Context, accountID, userID, networkID string) ([]*types.NetworkResource, error) {
ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, permissions.Networks, permissions.Read) ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Networks, operations.Read)
if err != nil { if err != nil {
return nil, status.NewPermissionValidationError(err) return nil, status.NewPermissionValidationError(err)
} }
@@ -59,7 +61,7 @@ func (m *managerImpl) GetAllResourcesInNetwork(ctx context.Context, accountID, u
} }
func (m *managerImpl) GetAllResourcesInAccount(ctx context.Context, accountID, userID string) ([]*types.NetworkResource, error) { func (m *managerImpl) GetAllResourcesInAccount(ctx context.Context, accountID, userID string) ([]*types.NetworkResource, error) {
ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, permissions.Networks, permissions.Read) ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Networks, operations.Read)
if err != nil { if err != nil {
return nil, status.NewPermissionValidationError(err) return nil, status.NewPermissionValidationError(err)
} }
@@ -71,7 +73,7 @@ func (m *managerImpl) GetAllResourcesInAccount(ctx context.Context, accountID, u
} }
func (m *managerImpl) GetAllResourceIDsInAccount(ctx context.Context, accountID, userID string) (map[string][]string, error) { func (m *managerImpl) GetAllResourceIDsInAccount(ctx context.Context, accountID, userID string) (map[string][]string, error) {
ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, permissions.Networks, permissions.Read) ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Networks, operations.Read)
if err != nil { if err != nil {
return nil, status.NewPermissionValidationError(err) return nil, status.NewPermissionValidationError(err)
} }
@@ -93,7 +95,7 @@ func (m *managerImpl) GetAllResourceIDsInAccount(ctx context.Context, accountID,
} }
func (m *managerImpl) CreateResource(ctx context.Context, userID string, resource *types.NetworkResource) (*types.NetworkResource, error) { func (m *managerImpl) CreateResource(ctx context.Context, userID string, resource *types.NetworkResource) (*types.NetworkResource, error) {
ok, err := m.permissionsManager.ValidateUserPermissions(ctx, resource.AccountID, userID, permissions.Networks, permissions.Write) ok, err := m.permissionsManager.ValidateUserPermissions(ctx, resource.AccountID, userID, modules.Networks, operations.Create)
if err != nil { if err != nil {
return nil, status.NewPermissionValidationError(err) return nil, status.NewPermissionValidationError(err)
} }
@@ -164,7 +166,7 @@ func (m *managerImpl) CreateResource(ctx context.Context, userID string, resourc
} }
func (m *managerImpl) GetResource(ctx context.Context, accountID, userID, networkID, resourceID string) (*types.NetworkResource, error) { func (m *managerImpl) GetResource(ctx context.Context, accountID, userID, networkID, resourceID string) (*types.NetworkResource, error) {
ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, permissions.Networks, permissions.Read) ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Networks, operations.Read)
if err != nil { if err != nil {
return nil, status.NewPermissionValidationError(err) return nil, status.NewPermissionValidationError(err)
} }
@@ -185,7 +187,7 @@ func (m *managerImpl) GetResource(ctx context.Context, accountID, userID, networ
} }
func (m *managerImpl) UpdateResource(ctx context.Context, userID string, resource *types.NetworkResource) (*types.NetworkResource, error) { func (m *managerImpl) UpdateResource(ctx context.Context, userID string, resource *types.NetworkResource) (*types.NetworkResource, error) {
ok, err := m.permissionsManager.ValidateUserPermissions(ctx, resource.AccountID, userID, permissions.Networks, permissions.Write) ok, err := m.permissionsManager.ValidateUserPermissions(ctx, resource.AccountID, userID, modules.Networks, operations.Update)
if err != nil { if err != nil {
return nil, status.NewPermissionValidationError(err) return nil, status.NewPermissionValidationError(err)
} }
@@ -305,7 +307,7 @@ func (m *managerImpl) updateResourceGroups(ctx context.Context, transaction stor
} }
func (m *managerImpl) DeleteResource(ctx context.Context, accountID, userID, networkID, resourceID string) error { func (m *managerImpl) DeleteResource(ctx context.Context, accountID, userID, networkID, resourceID string) error {
ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, permissions.Networks, permissions.Write) ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Networks, operations.Delete)
if err != nil { if err != nil {
return status.NewPermissionValidationError(err) return status.NewPermissionValidationError(err)
} }
@@ -17,7 +17,7 @@ import (
func Test_GetAllResourcesInNetworkReturnsResources(t *testing.T) { func Test_GetAllResourcesInNetworkReturnsResources(t *testing.T) {
ctx := context.Background() ctx := context.Background()
accountID := "testAccountId" accountID := "testAccountId"
userID := "allowedUser" userID := "testAdminId"
networkID := "testNetworkId" networkID := "testNetworkId"
store, cleanUp, err := store.NewTestStoreFromSQL(context.Background(), "../../testdata/networks.sql", t.TempDir()) store, cleanUp, err := store.NewTestStoreFromSQL(context.Background(), "../../testdata/networks.sql", t.TempDir())
@@ -25,7 +25,7 @@ func Test_GetAllResourcesInNetworkReturnsResources(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
t.Cleanup(cleanUp) t.Cleanup(cleanUp)
permissionsManager := permissions.NewManagerMock() permissionsManager := permissions.NewManager(store)
am := mock_server.MockAccountManager{} am := mock_server.MockAccountManager{}
groupsManager := groups.NewManagerMock() groupsManager := groups.NewManagerMock()
manager := NewManager(store, permissionsManager, groupsManager, &am) manager := NewManager(store, permissionsManager, groupsManager, &am)
@@ -38,7 +38,7 @@ func Test_GetAllResourcesInNetworkReturnsResources(t *testing.T) {
func Test_GetAllResourcesInNetworkReturnsPermissionDenied(t *testing.T) { func Test_GetAllResourcesInNetworkReturnsPermissionDenied(t *testing.T) {
ctx := context.Background() ctx := context.Background()
accountID := "testAccountId" accountID := "testAccountId"
userID := "invalidUser" userID := "testUserId"
networkID := "testNetworkId" networkID := "testNetworkId"
store, cleanUp, err := store.NewTestStoreFromSQL(context.Background(), "../../testdata/networks.sql", t.TempDir()) store, cleanUp, err := store.NewTestStoreFromSQL(context.Background(), "../../testdata/networks.sql", t.TempDir())
@@ -46,7 +46,7 @@ func Test_GetAllResourcesInNetworkReturnsPermissionDenied(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
t.Cleanup(cleanUp) t.Cleanup(cleanUp)
permissionsManager := permissions.NewManagerMock() permissionsManager := permissions.NewManager(store)
am := mock_server.MockAccountManager{} am := mock_server.MockAccountManager{}
groupsManager := groups.NewManagerMock() groupsManager := groups.NewManagerMock()
manager := NewManager(store, permissionsManager, groupsManager, &am) manager := NewManager(store, permissionsManager, groupsManager, &am)
@@ -59,14 +59,14 @@ func Test_GetAllResourcesInNetworkReturnsPermissionDenied(t *testing.T) {
func Test_GetAllResourcesInAccountReturnsResources(t *testing.T) { func Test_GetAllResourcesInAccountReturnsResources(t *testing.T) {
ctx := context.Background() ctx := context.Background()
accountID := "testAccountId" accountID := "testAccountId"
userID := "allowedUser" userID := "testAdminId"
store, cleanUp, err := store.NewTestStoreFromSQL(context.Background(), "../../testdata/networks.sql", t.TempDir()) store, cleanUp, err := store.NewTestStoreFromSQL(context.Background(), "../../testdata/networks.sql", t.TempDir())
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
t.Cleanup(cleanUp) t.Cleanup(cleanUp)
permissionsManager := permissions.NewManagerMock() permissionsManager := permissions.NewManager(store)
am := mock_server.MockAccountManager{} am := mock_server.MockAccountManager{}
groupsManager := groups.NewManagerMock() groupsManager := groups.NewManagerMock()
manager := NewManager(store, permissionsManager, groupsManager, &am) manager := NewManager(store, permissionsManager, groupsManager, &am)
@@ -79,14 +79,14 @@ func Test_GetAllResourcesInAccountReturnsResources(t *testing.T) {
func Test_GetAllResourcesInAccountReturnsPermissionDenied(t *testing.T) { func Test_GetAllResourcesInAccountReturnsPermissionDenied(t *testing.T) {
ctx := context.Background() ctx := context.Background()
accountID := "testAccountId" accountID := "testAccountId"
userID := "invalidUser" userID := "testUserId"
store, cleanUp, err := store.NewTestStoreFromSQL(context.Background(), "../../testdata/networks.sql", t.TempDir()) store, cleanUp, err := store.NewTestStoreFromSQL(context.Background(), "../../testdata/networks.sql", t.TempDir())
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
t.Cleanup(cleanUp) t.Cleanup(cleanUp)
permissionsManager := permissions.NewManagerMock() permissionsManager := permissions.NewManager(store)
am := mock_server.MockAccountManager{} am := mock_server.MockAccountManager{}
groupsManager := groups.NewManagerMock() groupsManager := groups.NewManagerMock()
manager := NewManager(store, permissionsManager, groupsManager, &am) manager := NewManager(store, permissionsManager, groupsManager, &am)
@@ -100,7 +100,7 @@ func Test_GetAllResourcesInAccountReturnsPermissionDenied(t *testing.T) {
func Test_GetResourceInNetworkReturnsResources(t *testing.T) { func Test_GetResourceInNetworkReturnsResources(t *testing.T) {
ctx := context.Background() ctx := context.Background()
accountID := "testAccountId" accountID := "testAccountId"
userID := "allowedUser" userID := "testAdminId"
networkID := "testNetworkId" networkID := "testNetworkId"
resourceID := "testResourceId" resourceID := "testResourceId"
@@ -109,7 +109,7 @@ func Test_GetResourceInNetworkReturnsResources(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
t.Cleanup(cleanUp) t.Cleanup(cleanUp)
permissionsManager := permissions.NewManagerMock() permissionsManager := permissions.NewManager(store)
am := mock_server.MockAccountManager{} am := mock_server.MockAccountManager{}
groupsManager := groups.NewManagerMock() groupsManager := groups.NewManagerMock()
manager := NewManager(store, permissionsManager, groupsManager, &am) manager := NewManager(store, permissionsManager, groupsManager, &am)
@@ -122,7 +122,7 @@ func Test_GetResourceInNetworkReturnsResources(t *testing.T) {
func Test_GetResourceInNetworkReturnsPermissionDenied(t *testing.T) { func Test_GetResourceInNetworkReturnsPermissionDenied(t *testing.T) {
ctx := context.Background() ctx := context.Background()
accountID := "testAccountId" accountID := "testAccountId"
userID := "invalidUser" userID := "testUserId"
networkID := "testNetworkId" networkID := "testNetworkId"
resourceID := "testResourceId" resourceID := "testResourceId"
@@ -131,7 +131,7 @@ func Test_GetResourceInNetworkReturnsPermissionDenied(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
t.Cleanup(cleanUp) t.Cleanup(cleanUp)
permissionsManager := permissions.NewManagerMock() permissionsManager := permissions.NewManager(store)
am := mock_server.MockAccountManager{} am := mock_server.MockAccountManager{}
groupsManager := groups.NewManagerMock() groupsManager := groups.NewManagerMock()
manager := NewManager(store, permissionsManager, groupsManager, &am) manager := NewManager(store, permissionsManager, groupsManager, &am)
@@ -144,7 +144,7 @@ func Test_GetResourceInNetworkReturnsPermissionDenied(t *testing.T) {
func Test_CreateResourceSuccessfully(t *testing.T) { func Test_CreateResourceSuccessfully(t *testing.T) {
ctx := context.Background() ctx := context.Background()
userID := "allowedUser" userID := "testAdminId"
resource := &types.NetworkResource{ resource := &types.NetworkResource{
AccountID: "testAccountId", AccountID: "testAccountId",
NetworkID: "testNetworkId", NetworkID: "testNetworkId",
@@ -158,7 +158,7 @@ func Test_CreateResourceSuccessfully(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
t.Cleanup(cleanUp) t.Cleanup(cleanUp)
permissionsManager := permissions.NewManagerMock() permissionsManager := permissions.NewManager(store)
am := mock_server.MockAccountManager{} am := mock_server.MockAccountManager{}
groupsManager := groups.NewManagerMock() groupsManager := groups.NewManagerMock()
manager := NewManager(store, permissionsManager, groupsManager, &am) manager := NewManager(store, permissionsManager, groupsManager, &am)
@@ -170,7 +170,7 @@ func Test_CreateResourceSuccessfully(t *testing.T) {
func Test_CreateResourceFailsWithPermissionDenied(t *testing.T) { func Test_CreateResourceFailsWithPermissionDenied(t *testing.T) {
ctx := context.Background() ctx := context.Background()
userID := "invalidUser" userID := "testUserId"
resource := &types.NetworkResource{ resource := &types.NetworkResource{
AccountID: "testAccountId", AccountID: "testAccountId",
NetworkID: "testNetworkId", NetworkID: "testNetworkId",
@@ -184,7 +184,7 @@ func Test_CreateResourceFailsWithPermissionDenied(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
t.Cleanup(cleanUp) t.Cleanup(cleanUp)
permissionsManager := permissions.NewManagerMock() permissionsManager := permissions.NewManager(store)
am := mock_server.MockAccountManager{} am := mock_server.MockAccountManager{}
groupsManager := groups.NewManagerMock() groupsManager := groups.NewManagerMock()
manager := NewManager(store, permissionsManager, groupsManager, &am) manager := NewManager(store, permissionsManager, groupsManager, &am)
@@ -197,7 +197,7 @@ func Test_CreateResourceFailsWithPermissionDenied(t *testing.T) {
func Test_CreateResourceFailsWithInvalidAddress(t *testing.T) { func Test_CreateResourceFailsWithInvalidAddress(t *testing.T) {
ctx := context.Background() ctx := context.Background()
userID := "allowedUser" userID := "testAdminId"
resource := &types.NetworkResource{ resource := &types.NetworkResource{
AccountID: "testAccountId", AccountID: "testAccountId",
NetworkID: "testNetworkId", NetworkID: "testNetworkId",
@@ -211,7 +211,7 @@ func Test_CreateResourceFailsWithInvalidAddress(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
t.Cleanup(cleanUp) t.Cleanup(cleanUp)
permissionsManager := permissions.NewManagerMock() permissionsManager := permissions.NewManager(store)
am := mock_server.MockAccountManager{} am := mock_server.MockAccountManager{}
groupsManager := groups.NewManagerMock() groupsManager := groups.NewManagerMock()
manager := NewManager(store, permissionsManager, groupsManager, &am) manager := NewManager(store, permissionsManager, groupsManager, &am)
@@ -223,7 +223,7 @@ func Test_CreateResourceFailsWithInvalidAddress(t *testing.T) {
func Test_CreateResourceFailsWithUsedName(t *testing.T) { func Test_CreateResourceFailsWithUsedName(t *testing.T) {
ctx := context.Background() ctx := context.Background()
userID := "allowedUser" userID := "testAdminId"
resource := &types.NetworkResource{ resource := &types.NetworkResource{
AccountID: "testAccountId", AccountID: "testAccountId",
NetworkID: "testNetworkId", NetworkID: "testNetworkId",
@@ -237,7 +237,7 @@ func Test_CreateResourceFailsWithUsedName(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
t.Cleanup(cleanUp) t.Cleanup(cleanUp)
permissionsManager := permissions.NewManagerMock() permissionsManager := permissions.NewManager(store)
am := mock_server.MockAccountManager{} am := mock_server.MockAccountManager{}
groupsManager := groups.NewManagerMock() groupsManager := groups.NewManagerMock()
manager := NewManager(store, permissionsManager, groupsManager, &am) manager := NewManager(store, permissionsManager, groupsManager, &am)
@@ -250,7 +250,7 @@ func Test_CreateResourceFailsWithUsedName(t *testing.T) {
func Test_UpdateResourceSuccessfully(t *testing.T) { func Test_UpdateResourceSuccessfully(t *testing.T) {
ctx := context.Background() ctx := context.Background()
accountID := "testAccountId" accountID := "testAccountId"
userID := "allowedUser" userID := "testAdminId"
networkID := "testNetworkId" networkID := "testNetworkId"
resourceID := "testResourceId" resourceID := "testResourceId"
resource := &types.NetworkResource{ resource := &types.NetworkResource{
@@ -267,7 +267,7 @@ func Test_UpdateResourceSuccessfully(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
t.Cleanup(cleanUp) t.Cleanup(cleanUp)
permissionsManager := permissions.NewManagerMock() permissionsManager := permissions.NewManager(store)
am := mock_server.MockAccountManager{} am := mock_server.MockAccountManager{}
groupsManager := groups.NewManagerMock() groupsManager := groups.NewManagerMock()
manager := NewManager(store, permissionsManager, groupsManager, &am) manager := NewManager(store, permissionsManager, groupsManager, &am)
@@ -283,7 +283,7 @@ func Test_UpdateResourceSuccessfully(t *testing.T) {
func Test_UpdateResourceFailsWithResourceNotFound(t *testing.T) { func Test_UpdateResourceFailsWithResourceNotFound(t *testing.T) {
ctx := context.Background() ctx := context.Background()
accountID := "testAccountId" accountID := "testAccountId"
userID := "allowedUser" userID := "testAdminId"
networkID := "testNetworkId" networkID := "testNetworkId"
resourceID := "otherResourceId" resourceID := "otherResourceId"
resource := &types.NetworkResource{ resource := &types.NetworkResource{
@@ -299,7 +299,7 @@ func Test_UpdateResourceFailsWithResourceNotFound(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
t.Cleanup(cleanUp) t.Cleanup(cleanUp)
permissionsManager := permissions.NewManagerMock() permissionsManager := permissions.NewManager(store)
am := mock_server.MockAccountManager{} am := mock_server.MockAccountManager{}
groupsManager := groups.NewManagerMock() groupsManager := groups.NewManagerMock()
manager := NewManager(store, permissionsManager, groupsManager, &am) manager := NewManager(store, permissionsManager, groupsManager, &am)
@@ -312,7 +312,7 @@ func Test_UpdateResourceFailsWithResourceNotFound(t *testing.T) {
func Test_UpdateResourceFailsWithNameInUse(t *testing.T) { func Test_UpdateResourceFailsWithNameInUse(t *testing.T) {
ctx := context.Background() ctx := context.Background()
accountID := "testAccountId" accountID := "testAccountId"
userID := "allowedUser" userID := "testAdminId"
networkID := "testNetworkId" networkID := "testNetworkId"
resourceID := "testResourceId" resourceID := "testResourceId"
resource := &types.NetworkResource{ resource := &types.NetworkResource{
@@ -329,7 +329,7 @@ func Test_UpdateResourceFailsWithNameInUse(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
t.Cleanup(cleanUp) t.Cleanup(cleanUp)
permissionsManager := permissions.NewManagerMock() permissionsManager := permissions.NewManager(store)
am := mock_server.MockAccountManager{} am := mock_server.MockAccountManager{}
groupsManager := groups.NewManagerMock() groupsManager := groups.NewManagerMock()
manager := NewManager(store, permissionsManager, groupsManager, &am) manager := NewManager(store, permissionsManager, groupsManager, &am)
@@ -342,7 +342,7 @@ func Test_UpdateResourceFailsWithNameInUse(t *testing.T) {
func Test_UpdateResourceFailsWithPermissionDenied(t *testing.T) { func Test_UpdateResourceFailsWithPermissionDenied(t *testing.T) {
ctx := context.Background() ctx := context.Background()
accountID := "testAccountId" accountID := "testAccountId"
userID := "invalidUser" userID := "testUserId"
networkID := "testNetworkId" networkID := "testNetworkId"
resourceID := "testResourceId" resourceID := "testResourceId"
resource := &types.NetworkResource{ resource := &types.NetworkResource{
@@ -358,7 +358,7 @@ func Test_UpdateResourceFailsWithPermissionDenied(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
t.Cleanup(cleanUp) t.Cleanup(cleanUp)
permissionsManager := permissions.NewManagerMock() permissionsManager := permissions.NewManager(store)
am := mock_server.MockAccountManager{} am := mock_server.MockAccountManager{}
groupsManager := groups.NewManagerMock() groupsManager := groups.NewManagerMock()
manager := NewManager(store, permissionsManager, groupsManager, &am) manager := NewManager(store, permissionsManager, groupsManager, &am)
@@ -371,7 +371,7 @@ func Test_UpdateResourceFailsWithPermissionDenied(t *testing.T) {
func Test_DeleteResourceSuccessfully(t *testing.T) { func Test_DeleteResourceSuccessfully(t *testing.T) {
ctx := context.Background() ctx := context.Background()
accountID := "testAccountId" accountID := "testAccountId"
userID := "allowedUser" userID := "testAdminId"
networkID := "testNetworkId" networkID := "testNetworkId"
resourceID := "testResourceId" resourceID := "testResourceId"
@@ -380,7 +380,7 @@ func Test_DeleteResourceSuccessfully(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
t.Cleanup(cleanUp) t.Cleanup(cleanUp)
permissionsManager := permissions.NewManagerMock() permissionsManager := permissions.NewManager(store)
am := mock_server.MockAccountManager{} am := mock_server.MockAccountManager{}
groupsManager := groups.NewManagerMock() groupsManager := groups.NewManagerMock()
manager := NewManager(store, permissionsManager, groupsManager, &am) manager := NewManager(store, permissionsManager, groupsManager, &am)
@@ -392,7 +392,7 @@ func Test_DeleteResourceSuccessfully(t *testing.T) {
func Test_DeleteResourceFailsWithPermissionDenied(t *testing.T) { func Test_DeleteResourceFailsWithPermissionDenied(t *testing.T) {
ctx := context.Background() ctx := context.Background()
accountID := "testAccountId" accountID := "testAccountId"
userID := "invalidUser" userID := "testUserId"
networkID := "testNetworkId" networkID := "testNetworkId"
resourceID := "testResourceId" resourceID := "testResourceId"
@@ -401,7 +401,7 @@ func Test_DeleteResourceFailsWithPermissionDenied(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
t.Cleanup(cleanUp) t.Cleanup(cleanUp)
permissionsManager := permissions.NewManagerMock() permissionsManager := permissions.NewManager(store)
am := mock_server.MockAccountManager{} am := mock_server.MockAccountManager{}
groupsManager := groups.NewManagerMock() groupsManager := groups.NewManagerMock()
manager := NewManager(store, permissionsManager, groupsManager, &am) manager := NewManager(store, permissionsManager, groupsManager, &am)
@@ -12,6 +12,8 @@ import (
"github.com/netbirdio/netbird/management/server/networks/routers/types" "github.com/netbirdio/netbird/management/server/networks/routers/types"
networkTypes "github.com/netbirdio/netbird/management/server/networks/types" networkTypes "github.com/netbirdio/netbird/management/server/networks/types"
"github.com/netbirdio/netbird/management/server/permissions" "github.com/netbirdio/netbird/management/server/permissions"
"github.com/netbirdio/netbird/management/server/permissions/modules"
"github.com/netbirdio/netbird/management/server/permissions/operations"
"github.com/netbirdio/netbird/management/server/status" "github.com/netbirdio/netbird/management/server/status"
"github.com/netbirdio/netbird/management/server/store" "github.com/netbirdio/netbird/management/server/store"
) )
@@ -44,7 +46,7 @@ func NewManager(store store.Store, permissionsManager permissions.Manager, accou
} }
func (m *managerImpl) GetAllRoutersInNetwork(ctx context.Context, accountID, userID, networkID string) ([]*types.NetworkRouter, error) { func (m *managerImpl) GetAllRoutersInNetwork(ctx context.Context, accountID, userID, networkID string) ([]*types.NetworkRouter, error) {
ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, permissions.Networks, permissions.Read) ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Networks, operations.Read)
if err != nil { if err != nil {
return nil, status.NewPermissionValidationError(err) return nil, status.NewPermissionValidationError(err)
} }
@@ -56,7 +58,7 @@ func (m *managerImpl) GetAllRoutersInNetwork(ctx context.Context, accountID, use
} }
func (m *managerImpl) GetAllRoutersInAccount(ctx context.Context, accountID, userID string) (map[string][]*types.NetworkRouter, error) { func (m *managerImpl) GetAllRoutersInAccount(ctx context.Context, accountID, userID string) (map[string][]*types.NetworkRouter, error) {
ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, permissions.Networks, permissions.Read) ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Networks, operations.Read)
if err != nil { if err != nil {
return nil, status.NewPermissionValidationError(err) return nil, status.NewPermissionValidationError(err)
} }
@@ -78,7 +80,7 @@ func (m *managerImpl) GetAllRoutersInAccount(ctx context.Context, accountID, use
} }
func (m *managerImpl) CreateRouter(ctx context.Context, userID string, router *types.NetworkRouter) (*types.NetworkRouter, error) { func (m *managerImpl) CreateRouter(ctx context.Context, userID string, router *types.NetworkRouter) (*types.NetworkRouter, error) {
ok, err := m.permissionsManager.ValidateUserPermissions(ctx, router.AccountID, userID, permissions.Networks, permissions.Write) ok, err := m.permissionsManager.ValidateUserPermissions(ctx, router.AccountID, userID, modules.Networks, operations.Create)
if err != nil { if err != nil {
return nil, status.NewPermissionValidationError(err) return nil, status.NewPermissionValidationError(err)
} }
@@ -126,7 +128,7 @@ func (m *managerImpl) CreateRouter(ctx context.Context, userID string, router *t
} }
func (m *managerImpl) GetRouter(ctx context.Context, accountID, userID, networkID, routerID string) (*types.NetworkRouter, error) { func (m *managerImpl) GetRouter(ctx context.Context, accountID, userID, networkID, routerID string) (*types.NetworkRouter, error) {
ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, permissions.Networks, permissions.Read) ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Networks, operations.Read)
if err != nil { if err != nil {
return nil, status.NewPermissionValidationError(err) return nil, status.NewPermissionValidationError(err)
} }
@@ -147,7 +149,7 @@ func (m *managerImpl) GetRouter(ctx context.Context, accountID, userID, networkI
} }
func (m *managerImpl) UpdateRouter(ctx context.Context, userID string, router *types.NetworkRouter) (*types.NetworkRouter, error) { func (m *managerImpl) UpdateRouter(ctx context.Context, userID string, router *types.NetworkRouter) (*types.NetworkRouter, error) {
ok, err := m.permissionsManager.ValidateUserPermissions(ctx, router.AccountID, userID, permissions.Networks, permissions.Write) ok, err := m.permissionsManager.ValidateUserPermissions(ctx, router.AccountID, userID, modules.Networks, operations.Update)
if err != nil { if err != nil {
return nil, status.NewPermissionValidationError(err) return nil, status.NewPermissionValidationError(err)
} }
@@ -193,7 +195,7 @@ func (m *managerImpl) UpdateRouter(ctx context.Context, userID string, router *t
} }
func (m *managerImpl) DeleteRouter(ctx context.Context, accountID, userID, networkID, routerID string) error { func (m *managerImpl) DeleteRouter(ctx context.Context, accountID, userID, networkID, routerID string) error {
ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, permissions.Networks, permissions.Write) ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Networks, operations.Delete)
if err != nil { if err != nil {
return status.NewPermissionValidationError(err) return status.NewPermissionValidationError(err)
} }
@@ -16,7 +16,7 @@ import (
func Test_GetAllRoutersInNetworkReturnsRouters(t *testing.T) { func Test_GetAllRoutersInNetworkReturnsRouters(t *testing.T) {
ctx := context.Background() ctx := context.Background()
accountID := "testAccountId" accountID := "testAccountId"
userID := "allowedUser" userID := "testAdminId"
networkID := "testNetworkId" networkID := "testNetworkId"
s, cleanUp, err := store.NewTestStoreFromSQL(context.Background(), "../../testdata/networks.sql", t.TempDir()) s, cleanUp, err := store.NewTestStoreFromSQL(context.Background(), "../../testdata/networks.sql", t.TempDir())
@@ -24,7 +24,7 @@ func Test_GetAllRoutersInNetworkReturnsRouters(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
t.Cleanup(cleanUp) t.Cleanup(cleanUp)
permissionsManager := permissions.NewManagerMock() permissionsManager := permissions.NewManager(s)
am := mock_server.MockAccountManager{} am := mock_server.MockAccountManager{}
manager := NewManager(s, permissionsManager, &am) manager := NewManager(s, permissionsManager, &am)
@@ -37,7 +37,7 @@ func Test_GetAllRoutersInNetworkReturnsRouters(t *testing.T) {
func Test_GetAllRoutersInNetworkReturnsPermissionDenied(t *testing.T) { func Test_GetAllRoutersInNetworkReturnsPermissionDenied(t *testing.T) {
ctx := context.Background() ctx := context.Background()
accountID := "testAccountId" accountID := "testAccountId"
userID := "invalidUser" userID := "testUserId"
networkID := "testNetworkId" networkID := "testNetworkId"
s, cleanUp, err := store.NewTestStoreFromSQL(context.Background(), "../../testdata/networks.sql", t.TempDir()) s, cleanUp, err := store.NewTestStoreFromSQL(context.Background(), "../../testdata/networks.sql", t.TempDir())
@@ -45,7 +45,7 @@ func Test_GetAllRoutersInNetworkReturnsPermissionDenied(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
t.Cleanup(cleanUp) t.Cleanup(cleanUp)
permissionsManager := permissions.NewManagerMock() permissionsManager := permissions.NewManager(s)
am := mock_server.MockAccountManager{} am := mock_server.MockAccountManager{}
manager := NewManager(s, permissionsManager, &am) manager := NewManager(s, permissionsManager, &am)
@@ -58,7 +58,7 @@ func Test_GetAllRoutersInNetworkReturnsPermissionDenied(t *testing.T) {
func Test_GetRouterReturnsRouter(t *testing.T) { func Test_GetRouterReturnsRouter(t *testing.T) {
ctx := context.Background() ctx := context.Background()
accountID := "testAccountId" accountID := "testAccountId"
userID := "allowedUser" userID := "testAdminId"
networkID := "testNetworkId" networkID := "testNetworkId"
resourceID := "testRouterId" resourceID := "testRouterId"
@@ -67,7 +67,7 @@ func Test_GetRouterReturnsRouter(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
t.Cleanup(cleanUp) t.Cleanup(cleanUp)
permissionsManager := permissions.NewManagerMock() permissionsManager := permissions.NewManager(s)
am := mock_server.MockAccountManager{} am := mock_server.MockAccountManager{}
manager := NewManager(s, permissionsManager, &am) manager := NewManager(s, permissionsManager, &am)
@@ -79,7 +79,7 @@ func Test_GetRouterReturnsRouter(t *testing.T) {
func Test_GetRouterReturnsPermissionDenied(t *testing.T) { func Test_GetRouterReturnsPermissionDenied(t *testing.T) {
ctx := context.Background() ctx := context.Background()
accountID := "testAccountId" accountID := "testAccountId"
userID := "invalidUser" userID := "testUserId"
networkID := "testNetworkId" networkID := "testNetworkId"
resourceID := "testRouterId" resourceID := "testRouterId"
@@ -88,7 +88,7 @@ func Test_GetRouterReturnsPermissionDenied(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
t.Cleanup(cleanUp) t.Cleanup(cleanUp)
permissionsManager := permissions.NewManagerMock() permissionsManager := permissions.NewManager(s)
am := mock_server.MockAccountManager{} am := mock_server.MockAccountManager{}
manager := NewManager(s, permissionsManager, &am) manager := NewManager(s, permissionsManager, &am)
@@ -100,7 +100,7 @@ func Test_GetRouterReturnsPermissionDenied(t *testing.T) {
func Test_CreateRouterSuccessfully(t *testing.T) { func Test_CreateRouterSuccessfully(t *testing.T) {
ctx := context.Background() ctx := context.Background()
userID := "allowedUser" userID := "testAdminId"
router, err := types.NewNetworkRouter("testAccountId", "testNetworkId", "testPeerId", []string{}, false, 9999, true) router, err := types.NewNetworkRouter("testAccountId", "testNetworkId", "testPeerId", []string{}, false, 9999, true)
if err != nil { if err != nil {
require.NoError(t, err) require.NoError(t, err)
@@ -111,7 +111,7 @@ func Test_CreateRouterSuccessfully(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
t.Cleanup(cleanUp) t.Cleanup(cleanUp)
permissionsManager := permissions.NewManagerMock() permissionsManager := permissions.NewManager(s)
am := mock_server.MockAccountManager{} am := mock_server.MockAccountManager{}
manager := NewManager(s, permissionsManager, &am) manager := NewManager(s, permissionsManager, &am)
@@ -126,7 +126,7 @@ func Test_CreateRouterSuccessfully(t *testing.T) {
func Test_CreateRouterFailsWithPermissionDenied(t *testing.T) { func Test_CreateRouterFailsWithPermissionDenied(t *testing.T) {
ctx := context.Background() ctx := context.Background()
userID := "invalidUser" userID := "testUserId"
router, err := types.NewNetworkRouter("testAccountId", "testNetworkId", "testPeerId", []string{}, false, 9999, true) router, err := types.NewNetworkRouter("testAccountId", "testNetworkId", "testPeerId", []string{}, false, 9999, true)
if err != nil { if err != nil {
require.NoError(t, err) require.NoError(t, err)
@@ -137,7 +137,7 @@ func Test_CreateRouterFailsWithPermissionDenied(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
t.Cleanup(cleanUp) t.Cleanup(cleanUp)
permissionsManager := permissions.NewManagerMock() permissionsManager := permissions.NewManager(s)
am := mock_server.MockAccountManager{} am := mock_server.MockAccountManager{}
manager := NewManager(s, permissionsManager, &am) manager := NewManager(s, permissionsManager, &am)
@@ -150,7 +150,7 @@ func Test_CreateRouterFailsWithPermissionDenied(t *testing.T) {
func Test_DeleteRouterSuccessfully(t *testing.T) { func Test_DeleteRouterSuccessfully(t *testing.T) {
ctx := context.Background() ctx := context.Background()
accountID := "testAccountId" accountID := "testAccountId"
userID := "allowedUser" userID := "testAdminId"
networkID := "testNetworkId" networkID := "testNetworkId"
routerID := "testRouterId" routerID := "testRouterId"
@@ -159,7 +159,7 @@ func Test_DeleteRouterSuccessfully(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
t.Cleanup(cleanUp) t.Cleanup(cleanUp)
permissionsManager := permissions.NewManagerMock() permissionsManager := permissions.NewManager(s)
am := mock_server.MockAccountManager{} am := mock_server.MockAccountManager{}
manager := NewManager(s, permissionsManager, &am) manager := NewManager(s, permissionsManager, &am)
@@ -170,7 +170,7 @@ func Test_DeleteRouterSuccessfully(t *testing.T) {
func Test_DeleteRouterFailsWithPermissionDenied(t *testing.T) { func Test_DeleteRouterFailsWithPermissionDenied(t *testing.T) {
ctx := context.Background() ctx := context.Background()
accountID := "testAccountId" accountID := "testAccountId"
userID := "invalidUser" userID := "testUserId"
networkID := "testNetworkId" networkID := "testNetworkId"
routerID := "testRouterId" routerID := "testRouterId"
@@ -179,7 +179,7 @@ func Test_DeleteRouterFailsWithPermissionDenied(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
t.Cleanup(cleanUp) t.Cleanup(cleanUp)
permissionsManager := permissions.NewManagerMock() permissionsManager := permissions.NewManager(s)
am := mock_server.MockAccountManager{} am := mock_server.MockAccountManager{}
manager := NewManager(s, permissionsManager, &am) manager := NewManager(s, permissionsManager, &am)
@@ -190,7 +190,7 @@ func Test_DeleteRouterFailsWithPermissionDenied(t *testing.T) {
func Test_UpdateRouterSuccessfully(t *testing.T) { func Test_UpdateRouterSuccessfully(t *testing.T) {
ctx := context.Background() ctx := context.Background()
userID := "allowedUser" userID := "testAdminId"
router, err := types.NewNetworkRouter("testAccountId", "testNetworkId", "testPeerId", []string{}, false, 1, true) router, err := types.NewNetworkRouter("testAccountId", "testNetworkId", "testPeerId", []string{}, false, 1, true)
if err != nil { if err != nil {
require.NoError(t, err) require.NoError(t, err)
@@ -201,7 +201,7 @@ func Test_UpdateRouterSuccessfully(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
t.Cleanup(cleanUp) t.Cleanup(cleanUp)
permissionsManager := permissions.NewManagerMock() permissionsManager := permissions.NewManager(s)
am := mock_server.MockAccountManager{} am := mock_server.MockAccountManager{}
manager := NewManager(s, permissionsManager, &am) manager := NewManager(s, permissionsManager, &am)
@@ -212,7 +212,7 @@ func Test_UpdateRouterSuccessfully(t *testing.T) {
func Test_UpdateRouterFailsWithPermissionDenied(t *testing.T) { func Test_UpdateRouterFailsWithPermissionDenied(t *testing.T) {
ctx := context.Background() ctx := context.Background()
userID := "invalidUser" userID := "testUserId"
router, err := types.NewNetworkRouter("testAccountId", "testNetworkId", "testPeerId", []string{}, false, 1, true) router, err := types.NewNetworkRouter("testAccountId", "testNetworkId", "testPeerId", []string{}, false, 1, true)
if err != nil { if err != nil {
require.NoError(t, err) require.NoError(t, err)
@@ -223,7 +223,7 @@ func Test_UpdateRouterFailsWithPermissionDenied(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
t.Cleanup(cleanUp) t.Cleanup(cleanUp)
permissionsManager := permissions.NewManagerMock() permissionsManager := permissions.NewManager(s)
am := mock_server.MockAccountManager{} am := mock_server.MockAccountManager{}
manager := NewManager(s, permissionsManager, &am) manager := NewManager(s, permissionsManager, &am)
+73 -56
View File
@@ -17,6 +17,8 @@ import (
"github.com/netbirdio/netbird/management/domain" "github.com/netbirdio/netbird/management/domain"
"github.com/netbirdio/netbird/management/server/geolocation" "github.com/netbirdio/netbird/management/server/geolocation"
"github.com/netbirdio/netbird/management/server/permissions/modules"
"github.com/netbirdio/netbird/management/server/permissions/operations"
"github.com/netbirdio/netbird/management/server/idp" "github.com/netbirdio/netbird/management/server/idp"
"github.com/netbirdio/netbird/management/server/posture" "github.com/netbirdio/netbird/management/server/posture"
@@ -37,17 +39,9 @@ func (am *DefaultAccountManager) GetPeers(ctx context.Context, accountID, userID
return nil, err return nil, err
} }
if err := am.permissionsManager.ValidateAccountAccess(ctx, accountID, user, false); err != nil { allowed, err := am.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Peers, operations.Read)
return nil, err
}
settings, err := am.Store.GetAccountSettings(ctx, store.LockingStrengthShare, accountID)
if err != nil { if err != nil {
return nil, err return nil, status.NewPermissionValidationError(err)
}
if user.IsRegularUser() && settings.RegularUsersViewBlocked {
return []*nbpeer.Peer{}, nil
} }
accountPeers, err := am.Store.GetAccountPeers(ctx, store.LockingStrengthShare, accountID, nameFilter, ipFilter) accountPeers, err := am.Store.GetAccountPeers(ctx, store.LockingStrengthShare, accountID, nameFilter, ipFilter)
@@ -67,10 +61,23 @@ func (am *DefaultAccountManager) GetPeers(ctx context.Context, accountID, userID
peersMap[peer.ID] = peer peersMap[peer.ID] = peer
} }
if user.IsAdminOrServiceUser() { if allowed {
return peers, nil return peers, nil
} }
settings, err := am.Store.GetAccountSettings(ctx, store.LockingStrengthShare, accountID)
if err != nil {
return nil, fmt.Errorf("failed to get account settings: %w", err)
}
if settings.RegularUsersViewBlocked {
return []*nbpeer.Peer{}, nil
}
return am.getUserAccessiblePeers(ctx, accountID, peersMap, peers)
}
func (am *DefaultAccountManager) getUserAccessiblePeers(ctx context.Context, accountID string, peersMap map[string]*nbpeer.Peer, peers []*nbpeer.Peer) ([]*nbpeer.Peer, error) {
account, err := am.requestBuffer.GetAccountWithBackpressure(ctx, accountID) account, err := am.requestBuffer.GetAccountWithBackpressure(ctx, accountID)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -135,7 +142,7 @@ func (am *DefaultAccountManager) MarkPeerConnected(ctx context.Context, peerPubK
if expired { if expired {
// we need to update other peers because when peer login expires all other peers are notified to disconnect from // we need to update other peers because when peer login expires all other peers are notified to disconnect from
// the expired one. Here we notify them that connection is now allowed again. // the expired one. Here we notify them that connection is now allowed again.
am.UpdateAccountPeers(ctx, accountID) am.BufferUpdateAccountPeers(ctx, accountID)
} }
return nil return nil
@@ -183,13 +190,12 @@ func (am *DefaultAccountManager) UpdatePeer(ctx context.Context, accountID, user
unlock := am.Store.AcquireWriteLockByUID(ctx, accountID) unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
defer unlock() defer unlock()
user, err := am.Store.GetUserByUserID(ctx, store.LockingStrengthShare, userID) allowed, err := am.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Peers, operations.Update)
if err != nil { if err != nil {
return nil, err return nil, status.NewPermissionValidationError(err)
} }
if !allowed {
if err := am.permissionsManager.ValidateAccountAccess(ctx, accountID, user, false); err != nil { return nil, status.NewPermissionDeniedError()
return nil, err
} }
var peer *nbpeer.Peer var peer *nbpeer.Peer
@@ -315,15 +321,12 @@ func (am *DefaultAccountManager) DeletePeer(ctx context.Context, accountID, peer
unlock := am.Store.AcquireWriteLockByUID(ctx, accountID) unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
defer unlock() defer unlock()
if userID != activity.SystemInitiator { allowed, err := am.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Peers, operations.Delete)
user, err := am.Store.GetUserByUserID(ctx, store.LockingStrengthShare, userID) if err != nil {
if err != nil { return status.NewPermissionValidationError(err)
return err }
} if !allowed {
return status.NewPermissionDeniedError()
if err := am.permissionsManager.ValidateAccountAccess(ctx, accountID, user, false); err != nil {
return err
}
} }
peerAccountID, err := am.Store.GetAccountIDByPeerID(ctx, store.LockingStrengthShare, peerID) peerAccountID, err := am.Store.GetAccountIDByPeerID(ctx, store.LockingStrengthShare, peerID)
@@ -383,7 +386,7 @@ func (am *DefaultAccountManager) DeletePeer(ctx context.Context, accountID, peer
} }
if updateAccountPeers { if updateAccountPeers {
am.UpdateAccountPeers(ctx, accountID) am.BufferUpdateAccountPeers(ctx, accountID)
} }
return nil return nil
@@ -653,7 +656,7 @@ func (am *DefaultAccountManager) AddPeer(ctx context.Context, setupKey, userID s
unlock = nil unlock = nil
if updateAccountPeers { if updateAccountPeers {
am.UpdateAccountPeers(ctx, accountID) am.BufferUpdateAccountPeers(ctx, accountID)
} }
return am.getValidatedPeerWithMap(ctx, false, accountID, newPeer) return am.getValidatedPeerWithMap(ctx, false, accountID, newPeer)
@@ -748,7 +751,7 @@ func (am *DefaultAccountManager) SyncPeer(ctx context.Context, sync types.PeerSy
} }
if isStatusChanged || sync.UpdateAccountPeers || (updated && len(postureChecks) > 0) { if isStatusChanged || sync.UpdateAccountPeers || (updated && len(postureChecks) > 0) {
am.UpdateAccountPeers(ctx, accountID) am.BufferUpdateAccountPeers(ctx, accountID)
} }
return am.getValidatedPeerWithMap(ctx, peerNotValid, accountID, peer) return am.getValidatedPeerWithMap(ctx, peerNotValid, accountID, peer)
@@ -893,7 +896,7 @@ func (am *DefaultAccountManager) LoginPeer(ctx context.Context, login types.Peer
unlockPeer = nil unlockPeer = nil
if updateRemotePeers || isStatusChanged || (isPeerUpdated && len(postureChecks) > 0) { if updateRemotePeers || isStatusChanged || (isPeerUpdated && len(postureChecks) > 0) {
am.UpdateAccountPeers(ctx, accountID) am.BufferUpdateAccountPeers(ctx, accountID)
} }
return am.getValidatedPeerWithMap(ctx, isRequiresApproval, accountID, peer) return am.getValidatedPeerWithMap(ctx, isRequiresApproval, accountID, peer)
@@ -1094,41 +1097,33 @@ func peerLoginExpired(ctx context.Context, peer *nbpeer.Peer, settings *types.Se
// GetPeer for a given accountID, peerID and userID error if not found. // GetPeer for a given accountID, peerID and userID error if not found.
func (am *DefaultAccountManager) GetPeer(ctx context.Context, accountID, peerID, userID string) (*nbpeer.Peer, error) { func (am *DefaultAccountManager) GetPeer(ctx context.Context, accountID, peerID, userID string) (*nbpeer.Peer, error) {
user, err := am.Store.GetUserByUserID(ctx, store.LockingStrengthShare, userID)
if err != nil {
return nil, err
}
if err := am.permissionsManager.ValidateAccountAccess(ctx, accountID, user, false); err != nil {
return nil, err
}
settings, err := am.Store.GetAccountSettings(ctx, store.LockingStrengthShare, accountID)
if err != nil {
return nil, err
}
if user.IsRegularUser() && settings.RegularUsersViewBlocked {
return nil, status.Errorf(status.Internal, "user %s has no access to his own peer %s under account %s", userID, peerID, accountID)
}
peer, err := am.Store.GetPeerByID(ctx, store.LockingStrengthShare, accountID, peerID) peer, err := am.Store.GetPeerByID(ctx, store.LockingStrengthShare, accountID, peerID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
allowed, err := am.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Peers, operations.Read)
if err != nil {
return nil, status.NewPermissionValidationError(err)
}
if allowed {
return peer, nil
}
user, err := am.Store.GetUserByUserID(ctx, store.LockingStrengthShare, userID)
if err != nil {
return nil, err
}
// if admin or user owns this peer, return peer // if admin or user owns this peer, return peer
if user.IsAdminOrServiceUser() || peer.UserID == userID { if user.IsAdminOrServiceUser() || peer.UserID == userID {
return peer, nil return peer, nil
} }
// it is also possible that user doesn't own the peer but some of his peers have access to it, return am.checkIfUserOwnsPeer(ctx, accountID, userID, peer)
// this is a valid case, show the peer as well. }
userPeers, err := am.Store.GetUserPeers(ctx, store.LockingStrengthShare, accountID, userID)
if err != nil {
return nil, err
}
func (am *DefaultAccountManager) checkIfUserOwnsPeer(ctx context.Context, accountID, userID string, peer *nbpeer.Peer) (*nbpeer.Peer, error) {
account, err := am.requestBuffer.GetAccountWithBackpressure(ctx, accountID) account, err := am.requestBuffer.GetAccountWithBackpressure(ctx, accountID)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -1139,16 +1134,23 @@ func (am *DefaultAccountManager) GetPeer(ctx context.Context, accountID, peerID,
return nil, err return nil, err
} }
// it is also possible that user doesn't own the peer but some of his peers have access to it,
// this is a valid case, show the peer as well.
userPeers, err := am.Store.GetUserPeers(ctx, store.LockingStrengthShare, accountID, userID)
if err != nil {
return nil, err
}
for _, p := range userPeers { for _, p := range userPeers {
aclPeers, _ := account.GetPeerConnectionResources(ctx, p.ID, approvedPeersMap) aclPeers, _ := account.GetPeerConnectionResources(ctx, p.ID, approvedPeersMap)
for _, aclPeer := range aclPeers { for _, aclPeer := range aclPeers {
if aclPeer.ID == peerID { if aclPeer.ID == peer.ID {
return peer, nil return peer, nil
} }
} }
} }
return nil, status.Errorf(status.Internal, "user %s has no access to peer %s under account %s", userID, peerID, accountID) return nil, status.Errorf(status.Internal, "user %s has no access to peer %s under account %s", userID, peer.ID, accountID)
} }
// UpdateAccountPeers updates all peers that belong to an account. // UpdateAccountPeers updates all peers that belong to an account.
@@ -1226,6 +1228,21 @@ func (am *DefaultAccountManager) UpdateAccountPeers(ctx context.Context, account
} }
} }
func (am *DefaultAccountManager) BufferUpdateAccountPeers(ctx context.Context, accountID string) {
mu, _ := am.accountUpdateLocks.LoadOrStore(accountID, &sync.Mutex{})
lock := mu.(*sync.Mutex)
if !lock.TryLock() {
return
}
go func() {
time.Sleep(time.Duration(am.updateAccountPeersBufferInterval.Load()))
lock.Unlock()
am.UpdateAccountPeers(ctx, accountID)
}()
}
// UpdateAccountPeer updates a single peer that belongs to an account. // UpdateAccountPeer updates a single peer that belongs to an account.
// Should be called when changes need to be synced to a specific peer only. // Should be called when changes need to be synced to a specific peer only.
func (am *DefaultAccountManager) UpdateAccountPeer(ctx context.Context, accountId string, peerId string) { func (am *DefaultAccountManager) UpdateAccountPeer(ctx context.Context, accountId string, peerId string) {
+13 -12
View File
@@ -303,12 +303,12 @@ func TestAccountManager_GetNetworkMapWithPolicy(t *testing.T) {
group1.Peers = append(group1.Peers, peer1.ID) group1.Peers = append(group1.Peers, peer1.ID)
group2.Peers = append(group2.Peers, peer2.ID) group2.Peers = append(group2.Peers, peer2.ID)
err = manager.SaveGroup(context.Background(), account.Id, userID, &group1) err = manager.SaveGroup(context.Background(), account.Id, userID, &group1, true)
if err != nil { if err != nil {
t.Errorf("expecting group1 to be added, got failure %v", err) t.Errorf("expecting group1 to be added, got failure %v", err)
return return
} }
err = manager.SaveGroup(context.Background(), account.Id, userID, &group2) err = manager.SaveGroup(context.Background(), account.Id, userID, &group2, true)
if err != nil { if err != nil {
t.Errorf("expecting group2 to be added, got failure %v", err) t.Errorf("expecting group2 to be added, got failure %v", err)
return return
@@ -327,7 +327,7 @@ func TestAccountManager_GetNetworkMapWithPolicy(t *testing.T) {
}, },
}, },
} }
policy, err = manager.SavePolicy(context.Background(), account.Id, userID, policy) policy, err = manager.SavePolicy(context.Background(), account.Id, userID, policy, true)
if err != nil { if err != nil {
t.Errorf("expecting rule to be added, got failure %v", err) t.Errorf("expecting rule to be added, got failure %v", err)
return return
@@ -375,7 +375,7 @@ func TestAccountManager_GetNetworkMapWithPolicy(t *testing.T) {
} }
policy.Enabled = false policy.Enabled = false
_, err = manager.SavePolicy(context.Background(), account.Id, userID, policy) _, err = manager.SavePolicy(context.Background(), account.Id, userID, policy, true)
if err != nil { if err != nil {
t.Errorf("expecting rule to be added, got failure %v", err) t.Errorf("expecting rule to be added, got failure %v", err)
return return
@@ -1264,9 +1264,9 @@ func Test_RegisterPeerByUser(t *testing.T) {
ctrl := gomock.NewController(t) ctrl := gomock.NewController(t)
t.Cleanup(ctrl.Finish) t.Cleanup(ctrl.Finish)
settingsMockManager := settings.NewMockManager(ctrl) settingsMockManager := settings.NewMockManager(ctrl)
permissionsManager := permissions.NewManager(s)
permissionsManagerMock := permissions.NewManagerMock() am, err := BuildManager(context.Background(), s, NewPeersUpdateManager(nil), nil, "", "netbird.cloud", eventStore, nil, false, MocIntegratedValidator{}, metrics, port_forwarding.NewControllerMock(), settingsMockManager, permissionsManager)
am, err := BuildManager(context.Background(), s, NewPeersUpdateManager(nil), nil, "", "netbird.cloud", eventStore, nil, false, MocIntegratedValidator{}, metrics, port_forwarding.NewControllerMock(), settingsMockManager, permissionsManagerMock)
assert.NoError(t, err) assert.NoError(t, err)
existingAccountID := "bf1c8084-ba50-4ce7-9439-34653001fc3b" existingAccountID := "bf1c8084-ba50-4ce7-9439-34653001fc3b"
@@ -1333,9 +1333,9 @@ func Test_RegisterPeerBySetupKey(t *testing.T) {
ctrl := gomock.NewController(t) ctrl := gomock.NewController(t)
t.Cleanup(ctrl.Finish) t.Cleanup(ctrl.Finish)
settingsMockManager := settings.NewMockManager(ctrl) settingsMockManager := settings.NewMockManager(ctrl)
permissionsManager := permissions.NewManager(s)
permissionsManagerMock := permissions.NewManagerMock() am, err := BuildManager(context.Background(), s, NewPeersUpdateManager(nil), nil, "", "netbird.cloud", eventStore, nil, false, MocIntegratedValidator{}, metrics, port_forwarding.NewControllerMock(), settingsMockManager, permissionsManager)
am, err := BuildManager(context.Background(), s, NewPeersUpdateManager(nil), nil, "", "netbird.cloud", eventStore, nil, false, MocIntegratedValidator{}, metrics, port_forwarding.NewControllerMock(), settingsMockManager, permissionsManagerMock)
assert.NoError(t, err) assert.NoError(t, err)
existingAccountID := "bf1c8084-ba50-4ce7-9439-34653001fc3b" existingAccountID := "bf1c8084-ba50-4ce7-9439-34653001fc3b"
@@ -1406,8 +1406,9 @@ func Test_RegisterPeerRollbackOnFailure(t *testing.T) {
t.Cleanup(ctrl.Finish) t.Cleanup(ctrl.Finish)
settingsMockManager := settings.NewMockManager(ctrl) settingsMockManager := settings.NewMockManager(ctrl)
permissionsManagerMock := permissions.NewManagerMock() permissionsManager := permissions.NewManager(s)
am, err := BuildManager(context.Background(), s, NewPeersUpdateManager(nil), nil, "", "netbird.cloud", eventStore, nil, false, MocIntegratedValidator{}, metrics, port_forwarding.NewControllerMock(), settingsMockManager, permissionsManagerMock)
am, err := BuildManager(context.Background(), s, NewPeersUpdateManager(nil), nil, "", "netbird.cloud", eventStore, nil, false, MocIntegratedValidator{}, metrics, port_forwarding.NewControllerMock(), settingsMockManager, permissionsManager)
assert.NoError(t, err) assert.NoError(t, err)
existingAccountID := "bf1c8084-ba50-4ce7-9439-34653001fc3b" existingAccountID := "bf1c8084-ba50-4ce7-9439-34653001fc3b"
@@ -1477,7 +1478,7 @@ func TestPeerAccountPeersUpdate(t *testing.T) {
Name: "GroupC", Name: "GroupC",
Peers: []string{}, Peers: []string{},
}, },
}) }, true)
require.NoError(t, err) require.NoError(t, err)
// create a user with auto groups // create a user with auto groups
@@ -1653,7 +1654,7 @@ func TestPeerAccountPeersUpdate(t *testing.T) {
Action: types.PolicyTrafficActionAccept, Action: types.PolicyTrafficActionAccept,
}, },
}, },
}) }, true)
require.NoError(t, err) require.NoError(t, err)
done := make(chan struct{}) done := make(chan struct{})
+5 -3
View File
@@ -8,6 +8,8 @@ import (
"github.com/netbirdio/netbird/management/server/peer" "github.com/netbirdio/netbird/management/server/peer"
"github.com/netbirdio/netbird/management/server/permissions" "github.com/netbirdio/netbird/management/server/permissions"
"github.com/netbirdio/netbird/management/server/permissions/modules"
"github.com/netbirdio/netbird/management/server/permissions/operations"
"github.com/netbirdio/netbird/management/server/status" "github.com/netbirdio/netbird/management/server/status"
"github.com/netbirdio/netbird/management/server/store" "github.com/netbirdio/netbird/management/server/store"
) )
@@ -31,7 +33,7 @@ func NewManager(store store.Store, permissionsManager permissions.Manager) Manag
} }
func (m *managerImpl) GetPeer(ctx context.Context, accountID, userID, peerID string) (*peer.Peer, error) { func (m *managerImpl) GetPeer(ctx context.Context, accountID, userID, peerID string) (*peer.Peer, error) {
allowed, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, permissions.Peers, permissions.Read) allowed, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Peers, operations.Read)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to validate user permissions: %w", err) return nil, fmt.Errorf("failed to validate user permissions: %w", err)
} }
@@ -44,13 +46,13 @@ func (m *managerImpl) GetPeer(ctx context.Context, accountID, userID, peerID str
} }
func (m *managerImpl) GetAllPeers(ctx context.Context, accountID, userID string) ([]*peer.Peer, error) { func (m *managerImpl) GetAllPeers(ctx context.Context, accountID, userID string) ([]*peer.Peer, error) {
allowed, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, permissions.Peers, permissions.Read) allowed, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Peers, operations.Read)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to validate user permissions: %w", err) return nil, fmt.Errorf("failed to validate user permissions: %w", err)
} }
if !allowed { if !allowed {
return nil, status.NewPermissionDeniedError() return m.store.GetUserPeers(ctx, store.LockingStrengthShare, accountID, userID)
} }
return m.store.GetAccountPeers(ctx, store.LockingStrengthShare, accountID, "", "") return m.store.GetAccountPeers(ctx, store.LockingStrengthShare, accountID, "", "")
+46 -77
View File
@@ -1,34 +1,24 @@
package permissions package permissions
//go:generate go run github.com/golang/mock/mockgen -package permissions -destination=manager_mock.go -source=./manager.go -build_flags=-mod=mod
import ( import (
"context" "context"
"errors"
"fmt"
log "github.com/sirupsen/logrus"
"github.com/netbirdio/netbird/management/server/activity"
"github.com/netbirdio/netbird/management/server/permissions/modules"
"github.com/netbirdio/netbird/management/server/permissions/operations"
"github.com/netbirdio/netbird/management/server/permissions/roles"
"github.com/netbirdio/netbird/management/server/status" "github.com/netbirdio/netbird/management/server/status"
"github.com/netbirdio/netbird/management/server/store" "github.com/netbirdio/netbird/management/server/store"
"github.com/netbirdio/netbird/management/server/types" "github.com/netbirdio/netbird/management/server/types"
) )
type Module string
const (
Networks Module = "networks"
Peers Module = "peers"
Groups Module = "groups"
Settings Module = "settings"
Accounts Module = "accounts"
)
type Operation string
const (
Read Operation = "read"
Write Operation = "write"
)
type Manager interface { type Manager interface {
ValidateUserPermissions(ctx context.Context, accountID, userID string, module Module, operation Operation) (bool, error) ValidateUserPermissions(ctx context.Context, accountID, userID string, module modules.Module, operation operations.Operation) (bool, error)
ValidateRoleModuleAccess(ctx context.Context, accountID string, role roles.RolePermissions, module modules.Module, operation operations.Operation) bool
ValidateAccountAccess(ctx context.Context, accountID string, user *types.User, allowOwnerAndAdmin bool) error ValidateAccountAccess(ctx context.Context, accountID string, user *types.User, allowOwnerAndAdmin bool) error
} }
@@ -36,16 +26,23 @@ type managerImpl struct {
store store.Store store store.Store
} }
type managerMock struct {
}
func NewManager(store store.Store) Manager { func NewManager(store store.Store) Manager {
return &managerImpl{ return &managerImpl{
store: store, store: store,
} }
} }
func (m *managerImpl) ValidateUserPermissions(ctx context.Context, accountID, userID string, module Module, operation Operation) (bool, error) { func (m *managerImpl) ValidateUserPermissions(
ctx context.Context,
accountID string,
userID string,
module modules.Module,
operation operations.Operation,
) (bool, error) {
if userID == activity.SystemInitiator {
return true, nil
}
user, err := m.store.GetUserByUserID(ctx, store.LockingStrengthShare, userID) user, err := m.store.GetUserByUserID(ctx, store.LockingStrengthShare, userID)
if err != nil { if err != nil {
return false, err return false, err
@@ -55,49 +52,42 @@ func (m *managerImpl) ValidateUserPermissions(ctx context.Context, accountID, us
return false, status.NewUserNotFoundError(userID) return false, status.NewUserNotFoundError(userID)
} }
if user.IsBlocked() {
return false, status.NewUserBlockedError()
}
if err := m.ValidateAccountAccess(ctx, accountID, user, false); err != nil { if err := m.ValidateAccountAccess(ctx, accountID, user, false); err != nil {
return false, err return false, err
} }
switch module { if operation == operations.Read && user.IsServiceUser {
case Accounts: return true, nil // this should be replaced by proper granular access role
if operation == Write && user.Role != types.UserRoleOwner {
return false, nil
}
return true, nil
default:
} }
switch user.Role { role, ok := roles.RolesMap[user.Role]
case types.UserRoleAdmin, types.UserRoleOwner: if !ok {
return true, nil return false, status.NewUserRoleNotFoundError(string(user.Role))
case types.UserRoleUser:
return m.validateRegularUserPermissions(ctx, accountID, module, operation)
case types.UserRoleBillingAdmin:
return false, nil
default:
return false, errors.New("invalid role")
} }
return m.ValidateRoleModuleAccess(ctx, accountID, role, module, operation), nil
} }
func (m *managerImpl) validateRegularUserPermissions(ctx context.Context, accountID string, module Module, operation Operation) (bool, error) { func (m *managerImpl) ValidateRoleModuleAccess(
settings, err := m.store.GetAccountSettings(ctx, store.LockingStrengthShare, accountID) ctx context.Context,
if err != nil { accountID string,
return false, fmt.Errorf("failed to get settings: %w", err) role roles.RolePermissions,
} module modules.Module,
if settings.RegularUsersViewBlocked { operation operations.Operation,
return false, nil ) bool {
if permissions, ok := role.Permissions[module]; ok {
if allowed, exists := permissions[operation]; exists {
return allowed
}
log.WithContext(ctx).Tracef("operation %s not found on module %s for role %s", operation, module, role.Role)
return false
} }
if operation == Write { return role.AutoAllowNew[operation]
return false, nil
}
if module == Peers {
return true, nil
}
return false, nil
} }
func (m *managerImpl) ValidateAccountAccess(ctx context.Context, accountID string, user *types.User, allowOwnerAndAdmin bool) error { func (m *managerImpl) ValidateAccountAccess(ctx context.Context, accountID string, user *types.User, allowOwnerAndAdmin bool) error {
@@ -106,24 +96,3 @@ func (m *managerImpl) ValidateAccountAccess(ctx context.Context, accountID strin
} }
return nil return nil
} }
func NewManagerMock() Manager {
return &managerMock{}
}
func (m *managerMock) ValidateUserPermissions(ctx context.Context, accountID, userID string, module Module, operation Operation) (bool, error) {
switch userID {
case "a23efe53-63fb-11ec-90d6-0242ac120003", "allowedUser", "testingUser", "account_creator", "serviceUserID", "test_user":
return true, nil
default:
return false, nil
}
}
func (m *managerMock) ValidateAccountAccess(ctx context.Context, accountID string, user *types.User, allowOwnerAndAdmin bool) error {
// @note managers explicitly checked this, so should the mock
if user.AccountID != accountID {
return status.NewUserNotPartOfAccountError()
}
return nil
}
@@ -0,0 +1,82 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: ./manager.go
// Package permissions is a generated GoMock package.
package permissions
import (
context "context"
reflect "reflect"
gomock "github.com/golang/mock/gomock"
modules "github.com/netbirdio/netbird/management/server/permissions/modules"
operations "github.com/netbirdio/netbird/management/server/permissions/operations"
roles "github.com/netbirdio/netbird/management/server/permissions/roles"
types "github.com/netbirdio/netbird/management/server/types"
)
// MockManager is a mock of Manager interface.
type MockManager struct {
ctrl *gomock.Controller
recorder *MockManagerMockRecorder
}
// MockManagerMockRecorder is the mock recorder for MockManager.
type MockManagerMockRecorder struct {
mock *MockManager
}
// NewMockManager creates a new mock instance.
func NewMockManager(ctrl *gomock.Controller) *MockManager {
mock := &MockManager{ctrl: ctrl}
mock.recorder = &MockManagerMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockManager) EXPECT() *MockManagerMockRecorder {
return m.recorder
}
// ValidateAccountAccess mocks base method.
func (m *MockManager) ValidateAccountAccess(ctx context.Context, accountID string, user *types.User, allowOwnerAndAdmin bool) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ValidateAccountAccess", ctx, accountID, user, allowOwnerAndAdmin)
ret0, _ := ret[0].(error)
return ret0
}
// ValidateAccountAccess indicates an expected call of ValidateAccountAccess.
func (mr *MockManagerMockRecorder) ValidateAccountAccess(ctx, accountID, user, allowOwnerAndAdmin interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateAccountAccess", reflect.TypeOf((*MockManager)(nil).ValidateAccountAccess), ctx, accountID, user, allowOwnerAndAdmin)
}
// ValidateRoleModuleAccess mocks base method.
func (m *MockManager) ValidateRoleModuleAccess(ctx context.Context, accountID string, role roles.RolePermissions, module modules.Module, operation operations.Operation) bool {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ValidateRoleModuleAccess", ctx, accountID, role, module, operation)
ret0, _ := ret[0].(bool)
return ret0
}
// ValidateRoleModuleAccess indicates an expected call of ValidateRoleModuleAccess.
func (mr *MockManagerMockRecorder) ValidateRoleModuleAccess(ctx, accountID, role, module, operation interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateRoleModuleAccess", reflect.TypeOf((*MockManager)(nil).ValidateRoleModuleAccess), ctx, accountID, role, module, operation)
}
// ValidateUserPermissions mocks base method.
func (m *MockManager) ValidateUserPermissions(ctx context.Context, accountID, userID string, module modules.Module, operation operations.Operation) (bool, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ValidateUserPermissions", ctx, accountID, userID, module, operation)
ret0, _ := ret[0].(bool)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// ValidateUserPermissions indicates an expected call of ValidateUserPermissions.
func (mr *MockManagerMockRecorder) ValidateUserPermissions(ctx, accountID, userID, module, operation interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateUserPermissions", reflect.TypeOf((*MockManager)(nil).ValidateUserPermissions), ctx, accountID, userID, module, operation)
}
@@ -0,0 +1,19 @@
package modules
type Module string
const (
Networks Module = "networks"
Peers Module = "peers"
Groups Module = "groups"
Settings Module = "settings"
Accounts Module = "accounts"
Dns Module = "dns"
Nameservers Module = "nameservers"
Events Module = "events"
Policies Module = "policies"
Routes Module = "routes"
Users Module = "users"
SetupKeys Module = "setup_keys"
Pats Module = "pats"
)
@@ -0,0 +1,10 @@
package operations
type Operation string
const (
Create Operation = "create"
Read Operation = "read"
Update Operation = "update"
Delete Operation = "delete"
)
@@ -0,0 +1,25 @@
package roles
import (
"github.com/netbirdio/netbird/management/server/permissions/modules"
"github.com/netbirdio/netbird/management/server/permissions/operations"
"github.com/netbirdio/netbird/management/server/types"
)
var Admin = RolePermissions{
Role: types.UserRoleAdmin,
AutoAllowNew: map[operations.Operation]bool{
operations.Read: true,
operations.Create: true,
operations.Update: true,
operations.Delete: true,
},
Permissions: Permissions{
modules.Accounts: {
operations.Read: true,
operations.Create: false,
operations.Update: false,
operations.Delete: false,
},
},
}
@@ -0,0 +1,16 @@
package roles
import (
"github.com/netbirdio/netbird/management/server/permissions/operations"
"github.com/netbirdio/netbird/management/server/types"
)
var Owner = RolePermissions{
Role: types.UserRoleOwner,
AutoAllowNew: map[operations.Operation]bool{
operations.Read: true,
operations.Create: true,
operations.Update: true,
operations.Delete: true,
},
}
@@ -0,0 +1,21 @@
package roles
import (
"github.com/netbirdio/netbird/management/server/permissions/modules"
"github.com/netbirdio/netbird/management/server/permissions/operations"
"github.com/netbirdio/netbird/management/server/types"
)
type RolePermissions struct {
Role types.UserRole
Permissions Permissions
AutoAllowNew map[operations.Operation]bool
}
type Permissions map[modules.Module]map[operations.Operation]bool
var RolesMap = map[types.UserRole]RolePermissions{
types.UserRoleOwner: Owner,
types.UserRoleAdmin: Admin,
types.UserRoleUser: User,
}
@@ -0,0 +1,16 @@
package roles
import (
"github.com/netbirdio/netbird/management/server/permissions/operations"
"github.com/netbirdio/netbird/management/server/types"
)
var User = RolePermissions{
Role: types.UserRoleUser,
AutoAllowNew: map[operations.Operation]bool{
operations.Read: false,
operations.Create: false,
operations.Update: false,
operations.Delete: false,
},
}
+23 -37
View File
@@ -7,6 +7,8 @@ import (
"github.com/rs/xid" "github.com/rs/xid"
"github.com/netbirdio/netbird/management/proto" "github.com/netbirdio/netbird/management/proto"
"github.com/netbirdio/netbird/management/server/permissions/modules"
"github.com/netbirdio/netbird/management/server/permissions/operations"
"github.com/netbirdio/netbird/management/server/store" "github.com/netbirdio/netbird/management/server/store"
"github.com/netbirdio/netbird/management/server/types" "github.com/netbirdio/netbird/management/server/types"
@@ -17,38 +19,32 @@ import (
// GetPolicy from the store // GetPolicy from the store
func (am *DefaultAccountManager) GetPolicy(ctx context.Context, accountID, policyID, userID string) (*types.Policy, error) { func (am *DefaultAccountManager) GetPolicy(ctx context.Context, accountID, policyID, userID string) (*types.Policy, error) {
user, err := am.Store.GetUserByUserID(ctx, store.LockingStrengthShare, userID) allowed, err := am.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Policies, operations.Read)
if err != nil { if err != nil {
return nil, err return nil, status.NewPermissionValidationError(err)
} }
if !allowed {
if err := am.permissionsManager.ValidateAccountAccess(ctx, accountID, user, false); err != nil { return nil, status.NewPermissionDeniedError()
return nil, err
}
if user.IsRegularUser() {
return nil, status.NewAdminPermissionError()
} }
return am.Store.GetPolicyByID(ctx, store.LockingStrengthShare, accountID, policyID) return am.Store.GetPolicyByID(ctx, store.LockingStrengthShare, accountID, policyID)
} }
// SavePolicy in the store // SavePolicy in the store
func (am *DefaultAccountManager) SavePolicy(ctx context.Context, accountID, userID string, policy *types.Policy) (*types.Policy, error) { func (am *DefaultAccountManager) SavePolicy(ctx context.Context, accountID, userID string, policy *types.Policy, create bool) (*types.Policy, error) {
unlock := am.Store.AcquireWriteLockByUID(ctx, accountID) unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
defer unlock() defer unlock()
user, err := am.Store.GetUserByUserID(ctx, store.LockingStrengthShare, userID) operation := operations.Create
if !create {
operation = operations.Update
}
allowed, err := am.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Policies, operation)
if err != nil { if err != nil {
return nil, err return nil, status.NewPermissionValidationError(err)
} }
if !allowed {
if err := am.permissionsManager.ValidateAccountAccess(ctx, accountID, user, false); err != nil { return nil, status.NewPermissionDeniedError()
return nil, err
}
if user.IsRegularUser() {
return nil, status.NewAdminPermissionError()
} }
var isUpdate = policy.ID != "" var isUpdate = policy.ID != ""
@@ -95,17 +91,12 @@ func (am *DefaultAccountManager) DeletePolicy(ctx context.Context, accountID, po
unlock := am.Store.AcquireWriteLockByUID(ctx, accountID) unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
defer unlock() defer unlock()
user, err := am.Store.GetUserByUserID(ctx, store.LockingStrengthShare, userID) allowed, err := am.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Policies, operations.Delete)
if err != nil { if err != nil {
return err return status.NewPermissionValidationError(err)
} }
if !allowed {
if err := am.permissionsManager.ValidateAccountAccess(ctx, accountID, user, false); err != nil { return status.NewPermissionDeniedError()
return err
}
if user.IsRegularUser() {
return status.NewAdminPermissionError()
} }
var policy *types.Policy var policy *types.Policy
@@ -143,17 +134,12 @@ func (am *DefaultAccountManager) DeletePolicy(ctx context.Context, accountID, po
// ListPolicies from the store. // ListPolicies from the store.
func (am *DefaultAccountManager) ListPolicies(ctx context.Context, accountID, userID string) ([]*types.Policy, error) { func (am *DefaultAccountManager) ListPolicies(ctx context.Context, accountID, userID string) ([]*types.Policy, error) {
user, err := am.Store.GetUserByUserID(ctx, store.LockingStrengthShare, userID) allowed, err := am.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Policies, operations.Read)
if err != nil { if err != nil {
return nil, err return nil, status.NewPermissionValidationError(err)
} }
if !allowed {
if err := am.permissionsManager.ValidateAccountAccess(ctx, accountID, user, false); err != nil { return nil, status.NewPermissionDeniedError()
return nil, err
}
if user.IsRegularUser() {
return nil, status.NewAdminPermissionError()
} }
return am.Store.GetAccountPolicies(ctx, store.LockingStrengthShare, accountID) return am.Store.GetAccountPolicies(ctx, store.LockingStrengthShare, accountID)
+8 -8
View File
@@ -883,7 +883,7 @@ func TestPolicyAccountPeersUpdate(t *testing.T) {
Name: "GroupD", Name: "GroupD",
Peers: []string{peer1.ID, peer2.ID}, Peers: []string{peer1.ID, peer2.ID},
}, },
}) }, true)
assert.NoError(t, err) assert.NoError(t, err)
updMsg := manager.peersUpdateManager.CreateChannel(context.Background(), peer1.ID) updMsg := manager.peersUpdateManager.CreateChannel(context.Background(), peer1.ID)
@@ -915,7 +915,7 @@ func TestPolicyAccountPeersUpdate(t *testing.T) {
Action: types.PolicyTrafficActionAccept, Action: types.PolicyTrafficActionAccept,
}, },
}, },
}) }, true)
assert.NoError(t, err) assert.NoError(t, err)
select { select {
@@ -947,7 +947,7 @@ func TestPolicyAccountPeersUpdate(t *testing.T) {
Action: types.PolicyTrafficActionAccept, Action: types.PolicyTrafficActionAccept,
}, },
}, },
}) }, true)
assert.NoError(t, err) assert.NoError(t, err)
select { select {
@@ -979,7 +979,7 @@ func TestPolicyAccountPeersUpdate(t *testing.T) {
Action: types.PolicyTrafficActionAccept, Action: types.PolicyTrafficActionAccept,
}, },
}, },
}) }, true)
assert.NoError(t, err) assert.NoError(t, err)
select { select {
@@ -1010,7 +1010,7 @@ func TestPolicyAccountPeersUpdate(t *testing.T) {
Action: types.PolicyTrafficActionAccept, Action: types.PolicyTrafficActionAccept,
}, },
}, },
}) }, true)
assert.NoError(t, err) assert.NoError(t, err)
select { select {
@@ -1030,7 +1030,7 @@ func TestPolicyAccountPeersUpdate(t *testing.T) {
}() }()
policyWithSourceAndDestinationPeers.Enabled = false policyWithSourceAndDestinationPeers.Enabled = false
policyWithSourceAndDestinationPeers, err = manager.SavePolicy(context.Background(), account.Id, userID, policyWithSourceAndDestinationPeers) policyWithSourceAndDestinationPeers, err = manager.SavePolicy(context.Background(), account.Id, userID, policyWithSourceAndDestinationPeers, true)
assert.NoError(t, err) assert.NoError(t, err)
select { select {
@@ -1051,7 +1051,7 @@ func TestPolicyAccountPeersUpdate(t *testing.T) {
policyWithSourceAndDestinationPeers.Description = "updated description" policyWithSourceAndDestinationPeers.Description = "updated description"
policyWithSourceAndDestinationPeers.Rules[0].Destinations = []string{"groupA"} policyWithSourceAndDestinationPeers.Rules[0].Destinations = []string{"groupA"}
policyWithSourceAndDestinationPeers, err = manager.SavePolicy(context.Background(), account.Id, userID, policyWithSourceAndDestinationPeers) policyWithSourceAndDestinationPeers, err = manager.SavePolicy(context.Background(), account.Id, userID, policyWithSourceAndDestinationPeers, true)
assert.NoError(t, err) assert.NoError(t, err)
select { select {
@@ -1071,7 +1071,7 @@ func TestPolicyAccountPeersUpdate(t *testing.T) {
}() }()
policyWithSourceAndDestinationPeers.Enabled = true policyWithSourceAndDestinationPeers.Enabled = true
policyWithSourceAndDestinationPeers, err = manager.SavePolicy(context.Background(), account.Id, userID, policyWithSourceAndDestinationPeers) policyWithSourceAndDestinationPeers, err = manager.SavePolicy(context.Background(), account.Id, userID, policyWithSourceAndDestinationPeers, true)
assert.NoError(t, err) assert.NoError(t, err)
select { select {
+23 -37
View File
@@ -10,6 +10,8 @@ import (
"golang.org/x/exp/maps" "golang.org/x/exp/maps"
"github.com/netbirdio/netbird/management/server/activity" "github.com/netbirdio/netbird/management/server/activity"
"github.com/netbirdio/netbird/management/server/permissions/modules"
"github.com/netbirdio/netbird/management/server/permissions/operations"
"github.com/netbirdio/netbird/management/server/posture" "github.com/netbirdio/netbird/management/server/posture"
"github.com/netbirdio/netbird/management/server/status" "github.com/netbirdio/netbird/management/server/status"
"github.com/netbirdio/netbird/management/server/store" "github.com/netbirdio/netbird/management/server/store"
@@ -17,38 +19,32 @@ import (
) )
func (am *DefaultAccountManager) GetPostureChecks(ctx context.Context, accountID, postureChecksID, userID string) (*posture.Checks, error) { func (am *DefaultAccountManager) GetPostureChecks(ctx context.Context, accountID, postureChecksID, userID string) (*posture.Checks, error) {
user, err := am.Store.GetUserByUserID(ctx, store.LockingStrengthShare, userID) allowed, err := am.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Policies, operations.Read)
if err != nil { if err != nil {
return nil, err return nil, status.NewPermissionValidationError(err)
} }
if !allowed {
if err := am.permissionsManager.ValidateAccountAccess(ctx, accountID, user, false); err != nil { return nil, status.NewPermissionDeniedError()
return nil, err
}
if !user.HasAdminPower() {
return nil, status.NewAdminPermissionError()
} }
return am.Store.GetPostureChecksByID(ctx, store.LockingStrengthShare, accountID, postureChecksID) return am.Store.GetPostureChecksByID(ctx, store.LockingStrengthShare, accountID, postureChecksID)
} }
// SavePostureChecks saves a posture check. // SavePostureChecks saves a posture check.
func (am *DefaultAccountManager) SavePostureChecks(ctx context.Context, accountID, userID string, postureChecks *posture.Checks) (*posture.Checks, error) { func (am *DefaultAccountManager) SavePostureChecks(ctx context.Context, accountID, userID string, postureChecks *posture.Checks, create bool) (*posture.Checks, error) {
unlock := am.Store.AcquireWriteLockByUID(ctx, accountID) unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
defer unlock() defer unlock()
user, err := am.Store.GetUserByUserID(ctx, store.LockingStrengthShare, userID) operation := operations.Create
if !create {
operation = operations.Update
}
allowed, err := am.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Policies, operation)
if err != nil { if err != nil {
return nil, err return nil, status.NewPermissionValidationError(err)
} }
if !allowed {
if err := am.permissionsManager.ValidateAccountAccess(ctx, accountID, user, false); err != nil { return nil, status.NewPermissionDeniedError()
return nil, err
}
if !user.HasAdminPower() {
return nil, status.NewAdminPermissionError()
} }
var updateAccountPeers bool var updateAccountPeers bool
@@ -94,17 +90,12 @@ func (am *DefaultAccountManager) DeletePostureChecks(ctx context.Context, accoun
unlock := am.Store.AcquireWriteLockByUID(ctx, accountID) unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
defer unlock() defer unlock()
user, err := am.Store.GetUserByUserID(ctx, store.LockingStrengthShare, userID) allowed, err := am.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Routes, operations.Read)
if err != nil { if err != nil {
return err return status.NewPermissionValidationError(err)
} }
if !allowed {
if err := am.permissionsManager.ValidateAccountAccess(ctx, accountID, user, false); err != nil { return status.NewPermissionDeniedError()
return err
}
if !user.HasAdminPower() {
return status.NewAdminPermissionError()
} }
var postureChecks *posture.Checks var postureChecks *posture.Checks
@@ -136,17 +127,12 @@ func (am *DefaultAccountManager) DeletePostureChecks(ctx context.Context, accoun
// ListPostureChecks returns a list of posture checks. // ListPostureChecks returns a list of posture checks.
func (am *DefaultAccountManager) ListPostureChecks(ctx context.Context, accountID, userID string) ([]*posture.Checks, error) { func (am *DefaultAccountManager) ListPostureChecks(ctx context.Context, accountID, userID string) ([]*posture.Checks, error) {
user, err := am.Store.GetUserByUserID(ctx, store.LockingStrengthShare, userID) allowed, err := am.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Policies, operations.Read)
if err != nil { if err != nil {
return nil, err return nil, status.NewPermissionValidationError(err)
} }
if !allowed {
if err := am.permissionsManager.ValidateAccountAccess(ctx, accountID, user, false); err != nil { return nil, status.NewPermissionDeniedError()
return nil, err
}
if !user.HasAdminPower() {
return nil, status.NewAdminPermissionError()
} }
return am.Store.GetAccountPostureChecks(ctx, store.LockingStrengthShare, accountID) return am.Store.GetAccountPostureChecks(ctx, store.LockingStrengthShare, accountID)
+24 -24
View File
@@ -33,7 +33,7 @@ func TestDefaultAccountManager_PostureCheck(t *testing.T) {
t.Run("Generic posture check flow", func(t *testing.T) { t.Run("Generic posture check flow", func(t *testing.T) {
// regular users can not create checks // regular users can not create checks
_, err = am.SavePostureChecks(context.Background(), account.Id, regularUserID, &posture.Checks{}) _, err = am.SavePostureChecks(context.Background(), account.Id, regularUserID, &posture.Checks{}, true)
assert.Error(t, err) assert.Error(t, err)
// regular users cannot list check // regular users cannot list check
@@ -48,7 +48,7 @@ func TestDefaultAccountManager_PostureCheck(t *testing.T) {
MinVersion: "0.26.0", MinVersion: "0.26.0",
}, },
}, },
}) }, true)
assert.NoError(t, err) assert.NoError(t, err)
// admin users can list check // admin users can list check
@@ -68,7 +68,7 @@ func TestDefaultAccountManager_PostureCheck(t *testing.T) {
}, },
}, },
}, },
}) }, true)
assert.Error(t, err) assert.Error(t, err)
// admins can update posture checks // admins can update posture checks
@@ -77,7 +77,7 @@ func TestDefaultAccountManager_PostureCheck(t *testing.T) {
MinVersion: "0.27.0", MinVersion: "0.27.0",
}, },
} }
_, err = am.SavePostureChecks(context.Background(), account.Id, adminUserID, postureCheck) _, err = am.SavePostureChecks(context.Background(), account.Id, adminUserID, postureCheck, true)
assert.NoError(t, err) assert.NoError(t, err)
// users should not be able to delete posture checks // users should not be able to delete posture checks
@@ -137,7 +137,7 @@ func TestPostureCheckAccountPeersUpdate(t *testing.T) {
Name: "GroupC", Name: "GroupC",
Peers: []string{}, Peers: []string{},
}, },
}) }, true)
assert.NoError(t, err) assert.NoError(t, err)
updMsg := manager.peersUpdateManager.CreateChannel(context.Background(), peer1.ID) updMsg := manager.peersUpdateManager.CreateChannel(context.Background(), peer1.ID)
@@ -156,7 +156,7 @@ func TestPostureCheckAccountPeersUpdate(t *testing.T) {
}, },
}, },
} }
postureCheckA, err = manager.SavePostureChecks(context.Background(), account.Id, userID, postureCheckA) postureCheckA, err = manager.SavePostureChecks(context.Background(), account.Id, userID, postureCheckA, true)
require.NoError(t, err) require.NoError(t, err)
postureCheckB := &posture.Checks{ postureCheckB := &posture.Checks{
@@ -177,7 +177,7 @@ func TestPostureCheckAccountPeersUpdate(t *testing.T) {
close(done) close(done)
}() }()
postureCheckB, err = manager.SavePostureChecks(context.Background(), account.Id, userID, postureCheckB) postureCheckB, err = manager.SavePostureChecks(context.Background(), account.Id, userID, postureCheckB, true)
assert.NoError(t, err) assert.NoError(t, err)
select { select {
@@ -200,7 +200,7 @@ func TestPostureCheckAccountPeersUpdate(t *testing.T) {
MinVersion: "0.29.0", MinVersion: "0.29.0",
}, },
} }
_, err = manager.SavePostureChecks(context.Background(), account.Id, userID, postureCheckB) _, err = manager.SavePostureChecks(context.Background(), account.Id, userID, postureCheckB, true)
assert.NoError(t, err) assert.NoError(t, err)
select { select {
@@ -232,7 +232,7 @@ func TestPostureCheckAccountPeersUpdate(t *testing.T) {
close(done) close(done)
}() }()
policy, err = manager.SavePolicy(context.Background(), account.Id, userID, policy) policy, err = manager.SavePolicy(context.Background(), account.Id, userID, policy, true)
assert.NoError(t, err) assert.NoError(t, err)
select { select {
@@ -261,7 +261,7 @@ func TestPostureCheckAccountPeersUpdate(t *testing.T) {
close(done) close(done)
}() }()
_, err = manager.SavePostureChecks(context.Background(), account.Id, userID, postureCheckB) _, err = manager.SavePostureChecks(context.Background(), account.Id, userID, postureCheckB, true)
assert.NoError(t, err) assert.NoError(t, err)
select { select {
@@ -280,7 +280,7 @@ func TestPostureCheckAccountPeersUpdate(t *testing.T) {
}() }()
policy.SourcePostureChecks = []string{} policy.SourcePostureChecks = []string{}
_, err := manager.SavePolicy(context.Background(), account.Id, userID, policy) _, err := manager.SavePolicy(context.Background(), account.Id, userID, policy, true)
assert.NoError(t, err) assert.NoError(t, err)
select { select {
@@ -308,7 +308,7 @@ func TestPostureCheckAccountPeersUpdate(t *testing.T) {
} }
}) })
_, err = manager.SavePostureChecks(context.Background(), account.Id, userID, postureCheckB) _, err = manager.SavePostureChecks(context.Background(), account.Id, userID, postureCheckB, true)
assert.NoError(t, err) assert.NoError(t, err)
// Updating linked posture check to policy with no peers should not trigger account peers update and not send peer update // Updating linked posture check to policy with no peers should not trigger account peers update and not send peer update
@@ -325,7 +325,7 @@ func TestPostureCheckAccountPeersUpdate(t *testing.T) {
}, },
}, },
SourcePostureChecks: []string{postureCheckB.ID}, SourcePostureChecks: []string{postureCheckB.ID},
}) }, true)
assert.NoError(t, err) assert.NoError(t, err)
done := make(chan struct{}) done := make(chan struct{})
@@ -339,7 +339,7 @@ func TestPostureCheckAccountPeersUpdate(t *testing.T) {
MinVersion: "0.29.0", MinVersion: "0.29.0",
}, },
} }
_, err = manager.SavePostureChecks(context.Background(), account.Id, userID, postureCheckB) _, err = manager.SavePostureChecks(context.Background(), account.Id, userID, postureCheckB, true)
assert.NoError(t, err) assert.NoError(t, err)
select { select {
@@ -369,7 +369,7 @@ func TestPostureCheckAccountPeersUpdate(t *testing.T) {
}, },
}, },
SourcePostureChecks: []string{postureCheckB.ID}, SourcePostureChecks: []string{postureCheckB.ID},
}) }, true)
assert.NoError(t, err) assert.NoError(t, err)
done := make(chan struct{}) done := make(chan struct{})
@@ -383,7 +383,7 @@ func TestPostureCheckAccountPeersUpdate(t *testing.T) {
MinVersion: "0.29.0", MinVersion: "0.29.0",
}, },
} }
_, err = manager.SavePostureChecks(context.Background(), account.Id, userID, postureCheckB) _, err = manager.SavePostureChecks(context.Background(), account.Id, userID, postureCheckB, true)
assert.NoError(t, err) assert.NoError(t, err)
select { select {
@@ -408,7 +408,7 @@ func TestPostureCheckAccountPeersUpdate(t *testing.T) {
}, },
}, },
SourcePostureChecks: []string{postureCheckB.ID}, SourcePostureChecks: []string{postureCheckB.ID},
}) }, true)
assert.NoError(t, err) assert.NoError(t, err)
done := make(chan struct{}) done := make(chan struct{})
@@ -426,7 +426,7 @@ func TestPostureCheckAccountPeersUpdate(t *testing.T) {
}, },
}, },
} }
_, err = manager.SavePostureChecks(context.Background(), account.Id, userID, postureCheckB) _, err = manager.SavePostureChecks(context.Background(), account.Id, userID, postureCheckB, true)
assert.NoError(t, err) assert.NoError(t, err)
select { select {
@@ -465,7 +465,7 @@ func TestArePostureCheckChangesAffectPeers(t *testing.T) {
NBVersionCheck: &posture.NBVersionCheck{MinVersion: "0.33.1"}, NBVersionCheck: &posture.NBVersionCheck{MinVersion: "0.33.1"},
}, },
} }
postureCheckA, err = manager.SavePostureChecks(context.Background(), account.Id, adminUserID, postureCheckA) postureCheckA, err = manager.SavePostureChecks(context.Background(), account.Id, adminUserID, postureCheckA, true)
require.NoError(t, err, "failed to save postureCheckA") require.NoError(t, err, "failed to save postureCheckA")
postureCheckB := &posture.Checks{ postureCheckB := &posture.Checks{
@@ -475,7 +475,7 @@ func TestArePostureCheckChangesAffectPeers(t *testing.T) {
NBVersionCheck: &posture.NBVersionCheck{MinVersion: "0.33.1"}, NBVersionCheck: &posture.NBVersionCheck{MinVersion: "0.33.1"},
}, },
} }
postureCheckB, err = manager.SavePostureChecks(context.Background(), account.Id, adminUserID, postureCheckB) postureCheckB, err = manager.SavePostureChecks(context.Background(), account.Id, adminUserID, postureCheckB, true)
require.NoError(t, err, "failed to save postureCheckB") require.NoError(t, err, "failed to save postureCheckB")
policy := &types.Policy{ policy := &types.Policy{
@@ -490,7 +490,7 @@ func TestArePostureCheckChangesAffectPeers(t *testing.T) {
SourcePostureChecks: []string{postureCheckA.ID}, SourcePostureChecks: []string{postureCheckA.ID},
} }
policy, err = manager.SavePolicy(context.Background(), account.Id, adminUserID, policy) policy, err = manager.SavePolicy(context.Background(), account.Id, adminUserID, policy, true)
require.NoError(t, err, "failed to save policy") require.NoError(t, err, "failed to save policy")
t.Run("posture check exists and is linked to policy with peers", func(t *testing.T) { t.Run("posture check exists and is linked to policy with peers", func(t *testing.T) {
@@ -514,7 +514,7 @@ func TestArePostureCheckChangesAffectPeers(t *testing.T) {
t.Run("posture check is linked to policy with no peers in source groups", func(t *testing.T) { t.Run("posture check is linked to policy with no peers in source groups", func(t *testing.T) {
policy.Rules[0].Sources = []string{"groupB"} policy.Rules[0].Sources = []string{"groupB"}
policy.Rules[0].Destinations = []string{"groupA"} policy.Rules[0].Destinations = []string{"groupA"}
_, err = manager.SavePolicy(context.Background(), account.Id, adminUserID, policy) _, err = manager.SavePolicy(context.Background(), account.Id, adminUserID, policy, true)
require.NoError(t, err, "failed to update policy") require.NoError(t, err, "failed to update policy")
result, err := arePostureCheckChangesAffectPeers(context.Background(), manager.Store, account.Id, postureCheckA.ID) result, err := arePostureCheckChangesAffectPeers(context.Background(), manager.Store, account.Id, postureCheckA.ID)
@@ -525,7 +525,7 @@ func TestArePostureCheckChangesAffectPeers(t *testing.T) {
t.Run("posture check is linked to policy with no peers in destination groups", func(t *testing.T) { t.Run("posture check is linked to policy with no peers in destination groups", func(t *testing.T) {
policy.Rules[0].Sources = []string{"groupA"} policy.Rules[0].Sources = []string{"groupA"}
policy.Rules[0].Destinations = []string{"groupB"} policy.Rules[0].Destinations = []string{"groupB"}
_, err = manager.SavePolicy(context.Background(), account.Id, adminUserID, policy) _, err = manager.SavePolicy(context.Background(), account.Id, adminUserID, policy, true)
require.NoError(t, err, "failed to update policy") require.NoError(t, err, "failed to update policy")
result, err := arePostureCheckChangesAffectPeers(context.Background(), manager.Store, account.Id, postureCheckA.ID) result, err := arePostureCheckChangesAffectPeers(context.Background(), manager.Store, account.Id, postureCheckA.ID)
@@ -546,7 +546,7 @@ func TestArePostureCheckChangesAffectPeers(t *testing.T) {
t.Run("posture check is linked to policy with non-existent group", func(t *testing.T) { t.Run("posture check is linked to policy with non-existent group", func(t *testing.T) {
policy.Rules[0].Sources = []string{"nonExistentGroup"} policy.Rules[0].Sources = []string{"nonExistentGroup"}
policy.Rules[0].Destinations = []string{"nonExistentGroup"} policy.Rules[0].Destinations = []string{"nonExistentGroup"}
_, err = manager.SavePolicy(context.Background(), account.Id, adminUserID, policy) _, err = manager.SavePolicy(context.Background(), account.Id, adminUserID, policy, true)
require.NoError(t, err, "failed to update policy") require.NoError(t, err, "failed to update policy")
result, err := arePostureCheckChangesAffectPeers(context.Background(), manager.Store, account.Id, postureCheckA.ID) result, err := arePostureCheckChangesAffectPeers(context.Background(), manager.Store, account.Id, postureCheckA.ID)
+22 -33
View File
@@ -8,6 +8,8 @@ import (
"github.com/rs/xid" "github.com/rs/xid"
"github.com/netbirdio/netbird/management/server/permissions/modules"
"github.com/netbirdio/netbird/management/server/permissions/operations"
"github.com/netbirdio/netbird/management/server/store" "github.com/netbirdio/netbird/management/server/store"
"github.com/netbirdio/netbird/management/server/types" "github.com/netbirdio/netbird/management/server/types"
@@ -20,17 +22,12 @@ import (
// GetRoute gets a route object from account and route IDs // GetRoute gets a route object from account and route IDs
func (am *DefaultAccountManager) GetRoute(ctx context.Context, accountID string, routeID route.ID, userID string) (*route.Route, error) { func (am *DefaultAccountManager) GetRoute(ctx context.Context, accountID string, routeID route.ID, userID string) (*route.Route, error) {
user, err := am.Store.GetUserByUserID(ctx, store.LockingStrengthShare, userID) allowed, err := am.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Routes, operations.Read)
if err != nil { if err != nil {
return nil, err return nil, status.NewPermissionValidationError(err)
} }
if !allowed {
if err := am.permissionsManager.ValidateAccountAccess(ctx, accountID, user, false); err != nil { return nil, status.NewPermissionDeniedError()
return nil, err
}
if !user.IsAdminOrServiceUser() {
return nil, status.Errorf(status.PermissionDenied, "only users with admin power can view Network Routes")
} }
return am.Store.GetRouteByID(ctx, store.LockingStrengthShare, string(routeID), accountID) return am.Store.GetRouteByID(ctx, store.LockingStrengthShare, string(routeID), accountID)
@@ -123,13 +120,12 @@ func (am *DefaultAccountManager) CreateRoute(ctx context.Context, accountID stri
unlock := am.Store.AcquireWriteLockByUID(ctx, accountID) unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
defer unlock() defer unlock()
user, err := am.Store.GetUserByUserID(ctx, store.LockingStrengthShare, userID) allowed, err := am.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Routes, operations.Create)
if err != nil { if err != nil {
return nil, err return nil, status.NewPermissionValidationError(err)
} }
if !allowed {
if err = am.permissionsManager.ValidateAccountAccess(ctx, accountID, user, false); err != nil { return nil, status.NewPermissionDeniedError()
return nil, err
} }
account, err := am.Store.GetAccount(ctx, accountID) account, err := am.Store.GetAccount(ctx, accountID)
@@ -242,13 +238,12 @@ func (am *DefaultAccountManager) SaveRoute(ctx context.Context, accountID, userI
return status.Errorf(status.InvalidArgument, "identifier should be between 1 and %d", route.MaxNetIDChar) return status.Errorf(status.InvalidArgument, "identifier should be between 1 and %d", route.MaxNetIDChar)
} }
user, err := am.Store.GetUserByUserID(ctx, store.LockingStrengthShare, userID) allowed, err := am.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Routes, operations.Update)
if err != nil { if err != nil {
return err return status.NewPermissionValidationError(err)
} }
if !allowed {
if err = am.permissionsManager.ValidateAccountAccess(ctx, accountID, user, false); err != nil { return status.NewPermissionDeniedError()
return err
} }
account, err := am.Store.GetAccount(ctx, accountID) account, err := am.Store.GetAccount(ctx, accountID)
@@ -318,13 +313,12 @@ func (am *DefaultAccountManager) DeleteRoute(ctx context.Context, accountID stri
unlock := am.Store.AcquireWriteLockByUID(ctx, accountID) unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
defer unlock() defer unlock()
user, err := am.Store.GetUserByUserID(ctx, store.LockingStrengthShare, userID) allowed, err := am.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Routes, operations.Delete)
if err != nil { if err != nil {
return err return status.NewPermissionValidationError(err)
} }
if !allowed {
if err = am.permissionsManager.ValidateAccountAccess(ctx, accountID, user, false); err != nil { return status.NewPermissionDeniedError()
return err
} }
account, err := am.Store.GetAccount(ctx, accountID) account, err := am.Store.GetAccount(ctx, accountID)
@@ -354,17 +348,12 @@ func (am *DefaultAccountManager) DeleteRoute(ctx context.Context, accountID stri
// ListRoutes returns a list of routes from account // ListRoutes returns a list of routes from account
func (am *DefaultAccountManager) ListRoutes(ctx context.Context, accountID, userID string) ([]*route.Route, error) { func (am *DefaultAccountManager) ListRoutes(ctx context.Context, accountID, userID string) ([]*route.Route, error) {
user, err := am.Store.GetUserByUserID(ctx, store.LockingStrengthShare, userID) allowed, err := am.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Routes, operations.Read)
if err != nil { if err != nil {
return nil, err return nil, status.NewPermissionValidationError(err)
} }
if !allowed {
if err := am.permissionsManager.ValidateAccountAccess(ctx, accountID, user, false); err != nil { return nil, status.NewPermissionDeniedError()
return nil, err
}
if !user.IsAdminOrServiceUser() {
return nil, status.Errorf(status.PermissionDenied, "only users with admin power can view Network Routes")
} }
return am.Store.GetAccountRoutes(ctx, store.LockingStrengthShare, accountID) return am.Store.GetAccountRoutes(ctx, store.LockingStrengthShare, accountID)
+9 -8
View File
@@ -1215,7 +1215,7 @@ func TestGetNetworkMap_RouteSync(t *testing.T) {
Name: "peer1 group", Name: "peer1 group",
Peers: []string{peer1ID}, Peers: []string{peer1ID},
} }
err = am.SaveGroup(context.Background(), account.Id, userID, newGroup) err = am.SaveGroup(context.Background(), account.Id, userID, newGroup, true)
require.NoError(t, err) require.NoError(t, err)
rules, err := am.ListPolicies(context.Background(), account.Id, "testingUser") rules, err := am.ListPolicies(context.Background(), account.Id, "testingUser")
@@ -1227,7 +1227,7 @@ func TestGetNetworkMap_RouteSync(t *testing.T) {
newPolicy.Rules[0].Sources = []string{newGroup.ID} newPolicy.Rules[0].Sources = []string{newGroup.ID}
newPolicy.Rules[0].Destinations = []string{newGroup.ID} newPolicy.Rules[0].Destinations = []string{newGroup.ID}
_, err = am.SavePolicy(context.Background(), account.Id, userID, newPolicy) _, err = am.SavePolicy(context.Background(), account.Id, userID, newPolicy, true)
require.NoError(t, err) require.NoError(t, err)
err = am.DeletePolicy(context.Background(), account.Id, defaultRule.ID, userID) err = am.DeletePolicy(context.Background(), account.Id, defaultRule.ID, userID)
@@ -1260,7 +1260,6 @@ func createRouterManager(t *testing.T) (*DefaultAccountManager, error) {
metrics, err := telemetry.NewDefaultAppMetrics(context.Background()) metrics, err := telemetry.NewDefaultAppMetrics(context.Background())
require.NoError(t, err) require.NoError(t, err)
permissionsManagerMock := permissions.NewManagerMock()
ctrl := gomock.NewController(t) ctrl := gomock.NewController(t)
t.Cleanup(ctrl.Finish) t.Cleanup(ctrl.Finish)
@@ -1283,7 +1282,9 @@ func createRouterManager(t *testing.T) (*DefaultAccountManager, error) {
AnyTimes(). AnyTimes().
Return(&types.ExtraSettings{}, nil) Return(&types.ExtraSettings{}, nil)
return BuildManager(context.Background(), store, NewPeersUpdateManager(nil), nil, "", "netbird.selfhosted", eventStore, nil, false, MocIntegratedValidator{}, metrics, port_forwarding.NewControllerMock(), settingsMockManager, permissionsManagerMock) permissionsManager := permissions.NewManager(store)
return BuildManager(context.Background(), store, NewPeersUpdateManager(nil), nil, "", "netbird.selfhosted", eventStore, nil, false, MocIntegratedValidator{}, metrics, port_forwarding.NewControllerMock(), settingsMockManager, permissionsManager)
} }
func createRouterStore(t *testing.T) (store.Store, error) { func createRouterStore(t *testing.T) (store.Store, error) {
@@ -1504,7 +1505,7 @@ func initTestRouteAccount(t *testing.T, am *DefaultAccountManager) (*types.Accou
} }
for _, group := range newGroup { for _, group := range newGroup {
err = am.SaveGroup(context.Background(), accountID, userID, group) err = am.SaveGroup(context.Background(), accountID, userID, group, true)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -1958,7 +1959,7 @@ func TestRouteAccountPeersUpdate(t *testing.T) {
Name: "GroupC", Name: "GroupC",
Peers: []string{}, Peers: []string{},
}, },
}) }, true)
assert.NoError(t, err) assert.NoError(t, err)
updMsg := manager.peersUpdateManager.CreateChannel(context.Background(), peer1ID) updMsg := manager.peersUpdateManager.CreateChannel(context.Background(), peer1ID)
@@ -2142,7 +2143,7 @@ func TestRouteAccountPeersUpdate(t *testing.T) {
ID: "groupB", ID: "groupB",
Name: "GroupB", Name: "GroupB",
Peers: []string{peer1ID}, Peers: []string{peer1ID},
}) }, true)
assert.NoError(t, err) assert.NoError(t, err)
select { select {
@@ -2182,7 +2183,7 @@ func TestRouteAccountPeersUpdate(t *testing.T) {
ID: "groupC", ID: "groupC",
Name: "GroupC", Name: "GroupC",
Peers: []string{peer1ID}, Peers: []string{peer1ID},
}) }, true)
assert.NoError(t, err) assert.NoError(t, err)
select { select {
+3 -1
View File
@@ -9,6 +9,8 @@ import (
"github.com/netbirdio/netbird/management/server/activity" "github.com/netbirdio/netbird/management/server/activity"
"github.com/netbirdio/netbird/management/server/integrations/extra_settings" "github.com/netbirdio/netbird/management/server/integrations/extra_settings"
"github.com/netbirdio/netbird/management/server/permissions" "github.com/netbirdio/netbird/management/server/permissions"
"github.com/netbirdio/netbird/management/server/permissions/modules"
"github.com/netbirdio/netbird/management/server/permissions/operations"
"github.com/netbirdio/netbird/management/server/status" "github.com/netbirdio/netbird/management/server/status"
"github.com/netbirdio/netbird/management/server/store" "github.com/netbirdio/netbird/management/server/store"
"github.com/netbirdio/netbird/management/server/types" "github.com/netbirdio/netbird/management/server/types"
@@ -44,7 +46,7 @@ func (m *managerImpl) GetExtraSettingsManager() extra_settings.Manager {
func (m *managerImpl) GetSettings(ctx context.Context, accountID, userID string) (*types.Settings, error) { func (m *managerImpl) GetSettings(ctx context.Context, accountID, userID string) (*types.Settings, error) {
if userID != activity.SystemInitiator { if userID != activity.SystemInitiator {
ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, permissions.Settings, permissions.Read) ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Settings, operations.Read)
if err != nil { if err != nil {
return nil, status.NewPermissionValidationError(err) return nil, status.NewPermissionValidationError(err)
} }
+22 -45
View File
@@ -8,6 +8,8 @@ import (
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/netbirdio/netbird/management/server/activity" "github.com/netbirdio/netbird/management/server/activity"
"github.com/netbirdio/netbird/management/server/permissions/modules"
"github.com/netbirdio/netbird/management/server/permissions/operations"
"github.com/netbirdio/netbird/management/server/status" "github.com/netbirdio/netbird/management/server/status"
"github.com/netbirdio/netbird/management/server/store" "github.com/netbirdio/netbird/management/server/store"
"github.com/netbirdio/netbird/management/server/types" "github.com/netbirdio/netbird/management/server/types"
@@ -56,17 +58,12 @@ func (am *DefaultAccountManager) CreateSetupKey(ctx context.Context, accountID s
unlock := am.Store.AcquireWriteLockByUID(ctx, accountID) unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
defer unlock() defer unlock()
user, err := am.Store.GetUserByUserID(ctx, store.LockingStrengthShare, userID) allowed, err := am.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.SetupKeys, operations.Create)
if err != nil { if err != nil {
return nil, err return nil, status.NewPermissionValidationError(err)
} }
if !allowed {
if err := am.permissionsManager.ValidateAccountAccess(ctx, accountID, user, false); err != nil { return nil, status.NewPermissionDeniedError()
return nil, err
}
if user.IsRegularUser() {
return nil, status.NewAdminPermissionError()
} }
var setupKey *types.SetupKey var setupKey *types.SetupKey
@@ -113,17 +110,12 @@ func (am *DefaultAccountManager) SaveSetupKey(ctx context.Context, accountID str
unlock := am.Store.AcquireWriteLockByUID(ctx, accountID) unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
defer unlock() defer unlock()
user, err := am.Store.GetUserByUserID(ctx, store.LockingStrengthShare, userID) allowed, err := am.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.SetupKeys, operations.Update)
if err != nil { if err != nil {
return nil, err return nil, status.NewPermissionValidationError(err)
} }
if !allowed {
if err := am.permissionsManager.ValidateAccountAccess(ctx, accountID, user, false); err != nil { return nil, status.NewPermissionDeniedError()
return nil, err
}
if user.IsRegularUser() {
return nil, status.NewAdminPermissionError()
} }
var oldKey *types.SetupKey var oldKey *types.SetupKey
@@ -175,17 +167,12 @@ func (am *DefaultAccountManager) SaveSetupKey(ctx context.Context, accountID str
// ListSetupKeys returns a list of all setup keys of the account // ListSetupKeys returns a list of all setup keys of the account
func (am *DefaultAccountManager) ListSetupKeys(ctx context.Context, accountID, userID string) ([]*types.SetupKey, error) { func (am *DefaultAccountManager) ListSetupKeys(ctx context.Context, accountID, userID string) ([]*types.SetupKey, error) {
user, err := am.Store.GetUserByUserID(ctx, store.LockingStrengthShare, userID) allowed, err := am.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.SetupKeys, operations.Read)
if err != nil { if err != nil {
return nil, err return nil, status.NewPermissionValidationError(err)
} }
if !allowed {
if err := am.permissionsManager.ValidateAccountAccess(ctx, accountID, user, false); err != nil { return nil, status.NewPermissionDeniedError()
return nil, err
}
if user.IsRegularUser() {
return nil, status.NewAdminPermissionError()
} }
return am.Store.GetAccountSetupKeys(ctx, store.LockingStrengthShare, accountID) return am.Store.GetAccountSetupKeys(ctx, store.LockingStrengthShare, accountID)
@@ -193,17 +180,12 @@ func (am *DefaultAccountManager) ListSetupKeys(ctx context.Context, accountID, u
// GetSetupKey looks up a SetupKey by KeyID, returns NotFound error if not found. // GetSetupKey looks up a SetupKey by KeyID, returns NotFound error if not found.
func (am *DefaultAccountManager) GetSetupKey(ctx context.Context, accountID, userID, keyID string) (*types.SetupKey, error) { func (am *DefaultAccountManager) GetSetupKey(ctx context.Context, accountID, userID, keyID string) (*types.SetupKey, error) {
user, err := am.Store.GetUserByUserID(ctx, store.LockingStrengthShare, userID) allowed, err := am.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.SetupKeys, operations.Read)
if err != nil { if err != nil {
return nil, err return nil, status.NewPermissionValidationError(err)
} }
if !allowed {
if err := am.permissionsManager.ValidateAccountAccess(ctx, accountID, user, false); err != nil { return nil, status.NewPermissionDeniedError()
return nil, err
}
if user.IsRegularUser() {
return nil, status.NewAdminPermissionError()
} }
setupKey, err := am.Store.GetSetupKeyByID(ctx, store.LockingStrengthShare, accountID, keyID) setupKey, err := am.Store.GetSetupKeyByID(ctx, store.LockingStrengthShare, accountID, keyID)
@@ -221,17 +203,12 @@ func (am *DefaultAccountManager) GetSetupKey(ctx context.Context, accountID, use
// DeleteSetupKey removes the setup key from the account // DeleteSetupKey removes the setup key from the account
func (am *DefaultAccountManager) DeleteSetupKey(ctx context.Context, accountID, userID, keyID string) error { func (am *DefaultAccountManager) DeleteSetupKey(ctx context.Context, accountID, userID, keyID string) error {
user, err := am.Store.GetUserByUserID(ctx, store.LockingStrengthShare, userID) allowed, err := am.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.SetupKeys, operations.Delete)
if err != nil { if err != nil {
return err return status.NewPermissionValidationError(err)
} }
if !allowed {
if err := am.permissionsManager.ValidateAccountAccess(ctx, accountID, user, false); err != nil { return status.NewPermissionDeniedError()
return err
}
if user.IsRegularUser() {
return status.NewAdminPermissionError()
} }
var deletedSetupKey *types.SetupKey var deletedSetupKey *types.SetupKey
+5 -5
View File
@@ -41,7 +41,7 @@ func TestDefaultAccountManager_SaveSetupKey(t *testing.T) {
Name: "group_name_2", Name: "group_name_2",
Peers: []string{}, Peers: []string{},
}, },
}) }, true)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -109,7 +109,7 @@ func TestDefaultAccountManager_CreateSetupKey(t *testing.T) {
ID: "group_1", ID: "group_1",
Name: "group_name_1", Name: "group_name_1",
Peers: []string{}, Peers: []string{},
}) }, true)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -118,7 +118,7 @@ func TestDefaultAccountManager_CreateSetupKey(t *testing.T) {
ID: "group_2", ID: "group_2",
Name: "group_name_2", Name: "group_name_2",
Peers: []string{}, Peers: []string{},
}) }, true)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -403,7 +403,7 @@ func TestSetupKeyAccountPeersUpdate(t *testing.T) {
ID: "groupA", ID: "groupA",
Name: "GroupA", Name: "GroupA",
Peers: []string{peer1.ID, peer2.ID, peer3.ID}, Peers: []string{peer1.ID, peer2.ID, peer3.ID},
}) }, true)
assert.NoError(t, err) assert.NoError(t, err)
policy := &types.Policy{ policy := &types.Policy{
@@ -418,7 +418,7 @@ func TestSetupKeyAccountPeersUpdate(t *testing.T) {
}, },
}, },
} }
_, err = manager.SavePolicy(context.Background(), account.Id, userID, policy) _, err = manager.SavePolicy(context.Background(), account.Id, userID, policy, true)
require.NoError(t, err) require.NoError(t, err)
updMsg := manager.peersUpdateManager.CreateChannel(context.Background(), peer1.ID) updMsg := manager.peersUpdateManager.CreateChannel(context.Background(), peer1.ID)

Some files were not shown because too many files have changed in this diff Show More