mirror of
https://github.com/netbirdio/netbird.git
synced 2026-08-20 22:51:30 +02:00
Compare commits
4 Commits
modify-pee
...
reverse-pr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2355d4947d | ||
|
|
11304900bb | ||
|
|
22852230b5 | ||
|
|
4efadd1e33 |
@@ -152,7 +152,6 @@ func NewClient(androidSDKVersion int, deviceName string, uiVersion string, tunAd
|
||||
execWorkaround(androidSDKVersion)
|
||||
|
||||
net.SetAndroidProtectSocketFn(tunAdapter.ProtectSocket)
|
||||
system.SetIFaceDiscover(iFaceDiscover)
|
||||
return &Client{
|
||||
deviceName: deviceName,
|
||||
uiVersion: uiVersion,
|
||||
|
||||
@@ -45,8 +45,8 @@ func daemonServerOptions(network string) []grpc.ServerOption {
|
||||
return nil
|
||||
}
|
||||
|
||||
creds := ipcauth.NewTransportCredentials() //nolint:staticcheck
|
||||
if creds == nil { //nolint:staticcheck // nil only on platforms without a peer-identity primitive
|
||||
creds := ipcauth.NewTransportCredentials()
|
||||
if creds == nil {
|
||||
log.Warnf("daemon IPC has no peer-identity primitive on %s: privileged operations will be denied", runtime.GOOS)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -27,8 +27,8 @@ func listenOnAddress(addr string) (*socketListener, error) {
|
||||
}
|
||||
|
||||
if network == "npipe" {
|
||||
listener, path, err := listenNamedPipe(address) //nolint:staticcheck
|
||||
if err != nil { //nolint:staticcheck // always errors on non-Windows builds
|
||||
listener, path, err := listenNamedPipe(address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &socketListener{Listener: listener, network: network, address: path}, nil
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"go.uber.org/mock/gomock"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.opentelemetry.io/otel"
|
||||
"google.golang.org/grpc"
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"go.uber.org/mock/gomock"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/grpc"
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"net/netip"
|
||||
"testing"
|
||||
|
||||
"go.uber.org/mock/gomock"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/google/gopacket"
|
||||
"github.com/google/gopacket/layers"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"net/netip"
|
||||
"testing"
|
||||
|
||||
"go.uber.org/mock/gomock"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/google/gopacket/layers"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"go.uber.org/mock/gomock"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/google/gopacket"
|
||||
"github.com/google/gopacket/layers"
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"net/netip"
|
||||
reflect "reflect"
|
||||
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
)
|
||||
|
||||
// MockPacketFilter is a mock of PacketFilter interface.
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
os "os"
|
||||
reflect "reflect"
|
||||
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
tun "golang.zx2c4.com/wireguard/tun"
|
||||
)
|
||||
|
||||
|
||||
@@ -53,15 +53,15 @@ func NewProxyBind(bind Bind, mtu uint16) *ProxyBind {
|
||||
return p
|
||||
}
|
||||
|
||||
// AddRelayedConn adds a new connection to the bind.
|
||||
// AddTurnConn adds a new connection to the bind.
|
||||
// endpoint is the NetBird address of the remote peer. The SetEndpoint return with the address what will be used in the
|
||||
// WireGuard configuration.
|
||||
//
|
||||
// Parameters:
|
||||
// - ctx: Context is used for proxyToLocal to avoid unnecessary error messages
|
||||
// - nbAddr: The NetBird UDP address of the remote peer, it required to generate fake address
|
||||
// - remoteConn: The established relayed connection to the remote peer
|
||||
func (p *ProxyBind) AddRelayedConn(ctx context.Context, nbAddr *net.UDPAddr, remoteConn net.Conn) error {
|
||||
// - remoteConn: The established TURN connection to the remote peer
|
||||
func (p *ProxyBind) AddTurnConn(ctx context.Context, nbAddr *net.UDPAddr, remoteConn net.Conn) error {
|
||||
fakeNetIP, err := fakeAddress(nbAddr)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -30,9 +30,9 @@ type WGEBPFProxy struct {
|
||||
proxyPort int
|
||||
mtu uint16
|
||||
|
||||
ebpfManager ebpfMgr.Manager
|
||||
relayedConnStore map[uint16]net.Conn
|
||||
relayedConnMutex sync.Mutex
|
||||
ebpfManager ebpfMgr.Manager
|
||||
turnConnStore map[uint16]net.Conn
|
||||
turnConnMutex sync.Mutex
|
||||
|
||||
lastUsedPort uint16
|
||||
rawConnIPv4 net.PacketConn
|
||||
@@ -50,7 +50,7 @@ func NewWGEBPFProxy(wgPort int, mtu uint16) *WGEBPFProxy {
|
||||
localWGListenPort: wgPort,
|
||||
mtu: mtu,
|
||||
ebpfManager: ebpf.GetEbpfManagerInstance(),
|
||||
relayedConnStore: make(map[uint16]net.Conn),
|
||||
turnConnStore: make(map[uint16]net.Conn),
|
||||
}
|
||||
return wgProxy
|
||||
}
|
||||
@@ -110,14 +110,14 @@ func (p *WGEBPFProxy) Listen() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddRelayedConn add new relayed connection for the proxy
|
||||
func (p *WGEBPFProxy) AddRelayedConn(relayedConn net.Conn) (*net.UDPAddr, error) {
|
||||
wgEndpointPort, err := p.storeRelayedConn(relayedConn)
|
||||
// AddTurnConn add new turn connection for the proxy
|
||||
func (p *WGEBPFProxy) AddTurnConn(turnConn net.Conn) (*net.UDPAddr, error) {
|
||||
wgEndpointPort, err := p.storeTurnConn(turnConn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Infof("relayed conn added to wg proxy store: %s, endpoint port: :%d", relayedConn.RemoteAddr(), wgEndpointPort)
|
||||
log.Infof("turn conn added to wg proxy store: %s, endpoint port: :%d", turnConn.RemoteAddr(), wgEndpointPort)
|
||||
|
||||
wgEndpoint := &net.UDPAddr{
|
||||
IP: net.ParseIP(loopbackAddr),
|
||||
@@ -186,48 +186,48 @@ func (p *WGEBPFProxy) readAndForwardPacket(buf []byte) error {
|
||||
return fmt.Errorf("failed to read UDP packet from WG: %w", err)
|
||||
}
|
||||
|
||||
p.relayedConnMutex.Lock()
|
||||
conn, ok := p.relayedConnStore[uint16(addr.Port)]
|
||||
p.relayedConnMutex.Unlock()
|
||||
p.turnConnMutex.Lock()
|
||||
conn, ok := p.turnConnStore[uint16(addr.Port)]
|
||||
p.turnConnMutex.Unlock()
|
||||
if !ok {
|
||||
if p.ctx.Err() == nil {
|
||||
log.Debugf("relayed conn not found by port because conn already has been closed: %d", addr.Port)
|
||||
log.Debugf("turn conn not found by port because conn already has been closed: %d", addr.Port)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if _, err := conn.Write(buf[:n]); err != nil {
|
||||
return fmt.Errorf("forward local WG packet (%d) to remote relayed conn: %w", addr.Port, err)
|
||||
return fmt.Errorf("failed to forward local WG packet (%d) to remote turn conn: %w", addr.Port, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *WGEBPFProxy) storeRelayedConn(relayedConn net.Conn) (uint16, error) {
|
||||
p.relayedConnMutex.Lock()
|
||||
defer p.relayedConnMutex.Unlock()
|
||||
func (p *WGEBPFProxy) storeTurnConn(turnConn net.Conn) (uint16, error) {
|
||||
p.turnConnMutex.Lock()
|
||||
defer p.turnConnMutex.Unlock()
|
||||
|
||||
np, err := p.nextFreePort()
|
||||
if err != nil {
|
||||
return np, err
|
||||
}
|
||||
p.relayedConnStore[np] = relayedConn
|
||||
p.turnConnStore[np] = turnConn
|
||||
return np, nil
|
||||
}
|
||||
|
||||
func (p *WGEBPFProxy) removeRelayedConn(relayedConnID uint16) {
|
||||
p.relayedConnMutex.Lock()
|
||||
defer p.relayedConnMutex.Unlock()
|
||||
func (p *WGEBPFProxy) removeTurnConn(turnConnID uint16) {
|
||||
p.turnConnMutex.Lock()
|
||||
defer p.turnConnMutex.Unlock()
|
||||
|
||||
_, ok := p.relayedConnStore[relayedConnID]
|
||||
_, ok := p.turnConnStore[turnConnID]
|
||||
if ok {
|
||||
log.Debugf("remove relayed conn from store by port: %d", relayedConnID)
|
||||
log.Debugf("remove turn conn from store by port: %d", turnConnID)
|
||||
}
|
||||
delete(p.relayedConnStore, relayedConnID)
|
||||
delete(p.turnConnStore, turnConnID)
|
||||
}
|
||||
|
||||
func (p *WGEBPFProxy) nextFreePort() (uint16, error) {
|
||||
if len(p.relayedConnStore) == 65535 {
|
||||
return 0, fmt.Errorf("reached maximum relayed connection numbers")
|
||||
if len(p.turnConnStore) == 65535 {
|
||||
return 0, fmt.Errorf("reached maximum turn connection numbers")
|
||||
}
|
||||
generatePort:
|
||||
if p.lastUsedPort == 65535 {
|
||||
@@ -236,7 +236,7 @@ generatePort:
|
||||
p.lastUsedPort++
|
||||
}
|
||||
|
||||
if _, ok := p.relayedConnStore[p.lastUsedPort]; ok {
|
||||
if _, ok := p.turnConnStore[p.lastUsedPort]; ok {
|
||||
goto generatePort
|
||||
}
|
||||
return p.lastUsedPort, nil
|
||||
|
||||
@@ -9,32 +9,32 @@ import (
|
||||
func TestWGEBPFProxy_connStore(t *testing.T) {
|
||||
wgProxy := NewWGEBPFProxy(1, 1280)
|
||||
|
||||
p, _ := wgProxy.storeRelayedConn(nil)
|
||||
p, _ := wgProxy.storeTurnConn(nil)
|
||||
if p != 1 {
|
||||
t.Errorf("invalid initial port: %d", wgProxy.lastUsedPort)
|
||||
}
|
||||
|
||||
numOfConns := 10
|
||||
for i := 0; i < numOfConns; i++ {
|
||||
p, _ = wgProxy.storeRelayedConn(nil)
|
||||
p, _ = wgProxy.storeTurnConn(nil)
|
||||
}
|
||||
if p != uint16(numOfConns)+1 {
|
||||
t.Errorf("invalid last used port: %d, expected: %d", p, numOfConns+1)
|
||||
}
|
||||
if len(wgProxy.relayedConnStore) != numOfConns+1 {
|
||||
t.Errorf("invalid store size: %d, expected: %d", len(wgProxy.relayedConnStore), numOfConns+1)
|
||||
if len(wgProxy.turnConnStore) != numOfConns+1 {
|
||||
t.Errorf("invalid store size: %d, expected: %d", len(wgProxy.turnConnStore), numOfConns+1)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWGEBPFProxy_portCalculation_overflow(t *testing.T) {
|
||||
wgProxy := NewWGEBPFProxy(1, 1280)
|
||||
|
||||
_, _ = wgProxy.storeRelayedConn(nil)
|
||||
_, _ = wgProxy.storeTurnConn(nil)
|
||||
wgProxy.lastUsedPort = 65535
|
||||
p, _ := wgProxy.storeRelayedConn(nil)
|
||||
p, _ := wgProxy.storeTurnConn(nil)
|
||||
|
||||
if len(wgProxy.relayedConnStore) != 2 {
|
||||
t.Errorf("invalid store size: %d, expected: %d", len(wgProxy.relayedConnStore), 2)
|
||||
if len(wgProxy.turnConnStore) != 2 {
|
||||
t.Errorf("invalid store size: %d, expected: %d", len(wgProxy.turnConnStore), 2)
|
||||
}
|
||||
|
||||
if p != 2 {
|
||||
@@ -46,11 +46,11 @@ func TestWGEBPFProxy_portCalculation_maxConn(t *testing.T) {
|
||||
wgProxy := NewWGEBPFProxy(1, 1280)
|
||||
|
||||
for i := 0; i < 65535; i++ {
|
||||
_, _ = wgProxy.storeRelayedConn(nil)
|
||||
_, _ = wgProxy.storeTurnConn(nil)
|
||||
}
|
||||
|
||||
_, err := wgProxy.storeRelayedConn(nil)
|
||||
_, err := wgProxy.storeTurnConn(nil)
|
||||
if err == nil {
|
||||
t.Errorf("invalid relayed conn store calculation")
|
||||
t.Errorf("invalid turn conn store calculation")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,10 +121,10 @@ func NewProxyWrapper(proxy *WGEBPFProxy) *ProxyWrapper {
|
||||
}
|
||||
}
|
||||
|
||||
func (p *ProxyWrapper) AddRelayedConn(ctx context.Context, _ *net.UDPAddr, remoteConn net.Conn) error {
|
||||
addr, err := p.wgeBPFProxy.AddRelayedConn(remoteConn)
|
||||
func (p *ProxyWrapper) AddTurnConn(ctx context.Context, _ *net.UDPAddr, remoteConn net.Conn) error {
|
||||
addr, err := p.wgeBPFProxy.AddTurnConn(remoteConn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("add relayed conn: %w", err)
|
||||
return fmt.Errorf("add turn conn: %w", err)
|
||||
}
|
||||
|
||||
headers, err := NewPacketHeaders(p.wgeBPFProxy.localWGListenPort, addr)
|
||||
@@ -252,7 +252,7 @@ func (p *ProxyWrapper) CloseConn() error {
|
||||
}
|
||||
|
||||
func (p *ProxyWrapper) proxyToLocal(ctx context.Context) {
|
||||
defer p.wgeBPFProxy.removeRelayedConn(uint16(p.wgRelayedEndpointAddr.Port))
|
||||
defer p.wgeBPFProxy.removeTurnConn(uint16(p.wgRelayedEndpointAddr.Port))
|
||||
|
||||
buf := make([]byte, p.wgeBPFProxy.mtu+bufsize.WGBufferOverhead)
|
||||
for {
|
||||
@@ -273,7 +273,7 @@ func (p *ProxyWrapper) proxyToLocal(ctx context.Context) {
|
||||
if ctx.Err() != nil {
|
||||
return
|
||||
}
|
||||
log.Errorf("failed to write out relayed pkg to local conn: %v", err)
|
||||
log.Errorf("failed to write out turn pkg to local conn: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -286,7 +286,7 @@ func (p *ProxyWrapper) readFromRemote(ctx context.Context, buf []byte) (int, err
|
||||
}
|
||||
p.closeListener.Notify()
|
||||
if !errors.Is(err, io.EOF) {
|
||||
log.Errorf("failed to read from relayed conn (endpoint: :%d): %s", p.wgRelayedEndpointAddr.Port, err)
|
||||
log.Errorf("failed to read from turn conn (endpoint: :%d): %s", p.wgRelayedEndpointAddr.Port, err)
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
|
||||
// Proxy is a transfer layer between the relayed connection and the WireGuard
|
||||
type Proxy interface {
|
||||
AddRelayedConn(ctx context.Context, endpoint *net.UDPAddr, remoteConn net.Conn) error
|
||||
AddTurnConn(ctx context.Context, endpoint *net.UDPAddr, remoteConn net.Conn) error
|
||||
EndpointAddr() *net.UDPAddr // EndpointAddr returns the address of the WireGuard peer endpoint
|
||||
Work() // Work start or resume the proxy
|
||||
Pause() // Pause to forward the packages from remote connection to WireGuard. The opposite way still works.
|
||||
|
||||
@@ -95,7 +95,7 @@ func TestProxyCloseByRemoteConn(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
addr, _ := net.ResolveUDPAddr("udp", "100.108.135.221:51892")
|
||||
relayedConn := newMockConn()
|
||||
err := tt.proxy.AddRelayedConn(ctx, addr, relayedConn)
|
||||
err := tt.proxy.AddTurnConn(ctx, addr, relayedConn)
|
||||
if err != nil {
|
||||
t.Errorf("error: %v", err)
|
||||
}
|
||||
@@ -157,7 +157,7 @@ func redirectTraffic(t *testing.T, proxy Proxy, wgPort int, endPointAddr *net.UD
|
||||
_ = relayedServer.Close()
|
||||
}()
|
||||
|
||||
if err := proxy.AddRelayedConn(context.Background(), endPointAddr, relayedConn); err != nil {
|
||||
if err := proxy.AddTurnConn(context.Background(), endPointAddr, relayedConn); err != nil {
|
||||
t.Errorf("error: %v", err)
|
||||
}
|
||||
defer func() {
|
||||
|
||||
@@ -119,9 +119,9 @@ func testRedirectAs(t *testing.T, proxy Proxy, wgPort int, nbAddr, p2pEndpoint *
|
||||
}
|
||||
defer relayConn.Close()
|
||||
|
||||
// Add relayed connection to proxy
|
||||
if err := proxy.AddRelayedConn(ctx, nbAddr, relayConn); err != nil {
|
||||
t.Fatalf("failed to add relayed connection: %v", err)
|
||||
// Add TURN connection to proxy
|
||||
if err := proxy.AddTurnConn(ctx, nbAddr, relayConn); err != nil {
|
||||
t.Fatalf("failed to add TURN connection: %v", err)
|
||||
}
|
||||
defer func() {
|
||||
if err := proxy.CloseConn(); err != nil {
|
||||
@@ -304,8 +304,8 @@ func TestRedirectAs_Multiple_Switches(t *testing.T) {
|
||||
Port: 38746,
|
||||
}
|
||||
|
||||
if err := proxy.AddRelayedConn(ctx, nbAddr, relayConn); err != nil {
|
||||
t.Fatalf("failed to add relayed connection: %v", err)
|
||||
if err := proxy.AddTurnConn(ctx, nbAddr, relayConn); err != nil {
|
||||
t.Fatalf("failed to add TURN connection: %v", err)
|
||||
}
|
||||
defer func() {
|
||||
if err := proxy.CloseConn(); err != nil {
|
||||
|
||||
@@ -51,12 +51,12 @@ func NewWGUDPProxy(wgPort int, mtu uint16) *WGUDPProxy {
|
||||
return p
|
||||
}
|
||||
|
||||
// AddRelayedConn dials the local WireGuard port and stores the relayed connection.
|
||||
// AddTurnConn
|
||||
// The provided Context must be non-nil. If the context expires before
|
||||
// the connection is complete, an error is returned. Once successfully
|
||||
// connected, any expiration of the context will not affect the
|
||||
// connection.
|
||||
func (p *WGUDPProxy) AddRelayedConn(ctx context.Context, _ *net.UDPAddr, remoteConn net.Conn) error {
|
||||
func (p *WGUDPProxy) AddTurnConn(ctx context.Context, _ *net.UDPAddr, remoteConn net.Conn) error {
|
||||
dialer := net.Dialer{}
|
||||
localConn, err := dialer.DialContext(ctx, "udp", fmt.Sprintf(":%d", p.localWGListenPort))
|
||||
if err != nil {
|
||||
|
||||
@@ -116,11 +116,11 @@ func (d *DefaultManager) ApplyFiltering(networkMap *mgmProto.NetworkMap, dnsRout
|
||||
// firewall state, so an identical hash means an identical resulting ruleset.
|
||||
func (d *DefaultManager) firewallConfigHash(networkMap *mgmProto.NetworkMap, dnsRouteFeatureFlag bool) (uint64, error) {
|
||||
return hashstructure.Hash(struct {
|
||||
PeerRules []*mgmProto.FirewallRule
|
||||
PeerRulesIsEmpty bool
|
||||
RouteRules []*mgmProto.RouteFirewallRule
|
||||
RouteRulesIsEmpty bool
|
||||
DNSRouteFeatureFlag bool
|
||||
PeerRules []*mgmProto.FirewallRule
|
||||
PeerRulesIsEmpty bool
|
||||
RouteRules []*mgmProto.RouteFirewallRule
|
||||
RouteRulesIsEmpty bool
|
||||
DNSRouteFeatureFlag bool
|
||||
}{
|
||||
PeerRules: networkMap.GetFirewallRules(),
|
||||
PeerRulesIsEmpty: networkMap.GetFirewallRulesIsEmpty(),
|
||||
@@ -144,13 +144,13 @@ func (d *DefaultManager) applyPeerACLs(networkMap *mgmProto.NetworkMap) {
|
||||
log.Warn("this peer is connected to a NetBird Management service with an older version. Allowing all traffic from connected peers")
|
||||
rules = append(rules,
|
||||
&mgmProto.FirewallRule{
|
||||
PeerIP: "0.0.0.0", //nolint:staticcheck
|
||||
PeerIP: "0.0.0.0",
|
||||
Direction: mgmProto.RuleDirection_IN,
|
||||
Action: mgmProto.RuleAction_ACCEPT,
|
||||
Protocol: mgmProto.RuleProtocol_ALL,
|
||||
},
|
||||
&mgmProto.FirewallRule{
|
||||
PeerIP: "0.0.0.0", //nolint:staticcheck
|
||||
PeerIP: "0.0.0.0",
|
||||
Direction: mgmProto.RuleDirection_OUT,
|
||||
Action: mgmProto.RuleAction_ACCEPT,
|
||||
Protocol: mgmProto.RuleProtocol_ALL,
|
||||
@@ -407,6 +407,7 @@ func (d *DefaultManager) getRuleGroupingSelector(rule *mgmProto.FirewallRule) st
|
||||
return fmt.Sprintf("%v:%v:%v:%s:%v", strconv.Itoa(int(rule.Direction)), rule.Action, rule.Protocol, rule.Port, rule.PortInfo)
|
||||
}
|
||||
|
||||
|
||||
// extractRuleIP extracts the peer IP from a firewall rule.
|
||||
// If sourcePrefixes is populated (new management), decode the first entry and use its address.
|
||||
// Otherwise fall back to the deprecated PeerIP string field (old management).
|
||||
|
||||
@@ -5,9 +5,9 @@ import (
|
||||
"net/netip"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/mock/gomock"
|
||||
|
||||
"github.com/netbirdio/netbird/client/firewall"
|
||||
"github.com/netbirdio/netbird/client/iface"
|
||||
@@ -87,7 +87,7 @@ func TestDefaultManager(t *testing.T) {
|
||||
networkMap.FirewallRules = append(
|
||||
networkMap.FirewallRules,
|
||||
&mgmProto.FirewallRule{
|
||||
PeerIP: "10.93.0.3", //nolint:staticcheck
|
||||
PeerIP: "10.93.0.3",
|
||||
Direction: mgmProto.RuleDirection_IN,
|
||||
Action: mgmProto.RuleAction_DROP,
|
||||
Protocol: mgmProto.RuleProtocol_ICMP,
|
||||
@@ -556,12 +556,12 @@ func TestApplyFilteringSkipsUnchangedConfig(t *testing.T) {
|
||||
|
||||
func buildNetworkMap(peerRules, routeRules int) *mgmProto.NetworkMap {
|
||||
nm := &mgmProto.NetworkMap{
|
||||
FirewallRulesIsEmpty: peerRules == 0,
|
||||
FirewallRulesIsEmpty: peerRules == 0,
|
||||
RoutesFirewallRulesIsEmpty: routeRules == 0,
|
||||
}
|
||||
for i := range peerRules {
|
||||
nm.FirewallRules = append(nm.FirewallRules, &mgmProto.FirewallRule{
|
||||
PeerIP: fmt.Sprintf("10.%d.%d.%d", i>>16&0xff, i>>8&0xff, i&0xff), //nolint:staticcheck
|
||||
PeerIP: fmt.Sprintf("10.%d.%d.%d", i>>16&0xff, i>>8&0xff, i&0xff),
|
||||
Direction: mgmProto.RuleDirection_IN,
|
||||
Action: mgmProto.RuleAction_ACCEPT,
|
||||
Protocol: mgmProto.RuleProtocol_TCP,
|
||||
|
||||
@@ -7,7 +7,7 @@ package mocks
|
||||
import (
|
||||
reflect "reflect"
|
||||
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
wgdevice "golang.zx2c4.com/wireguard/device"
|
||||
|
||||
"github.com/netbirdio/netbird/client/iface/device"
|
||||
|
||||
@@ -459,7 +459,7 @@ func (r *registryConfigurator) flushDNSCache() {
|
||||
|
||||
ret, _, err := dnsFlushResolverCacheFn.Call()
|
||||
if ret == 0 {
|
||||
if !errors.Is(err, syscall.Errno(0)) {
|
||||
if err != nil && !errors.Is(err, syscall.Errno(0)) {
|
||||
log.Errorf("DnsFlushResolverCache failed: %v", err)
|
||||
return
|
||||
}
|
||||
@@ -627,7 +627,7 @@ func refreshGroupPolicy() error {
|
||||
)
|
||||
|
||||
if ret == 0 {
|
||||
if !errors.Is(err, syscall.Errno(0)) {
|
||||
if err != nil && !errors.Is(err, syscall.Errno(0)) {
|
||||
return fmt.Errorf("RefreshPolicyEx failed: %w", err)
|
||||
}
|
||||
return fmt.Errorf("RefreshPolicyEx failed")
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"go.uber.org/mock/gomock"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/google/gopacket"
|
||||
"github.com/google/gopacket/layers"
|
||||
"github.com/miekg/dns"
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"go.uber.org/mock/gomock"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/miekg/dns"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
|
||||
@@ -101,7 +101,7 @@ func (m *Manager) Start(fwdEntries []*ForwarderEntry) error {
|
||||
m.dnsForwarder = NewDNSForwarder(listenAddress, dnsTTL, m.firewall, m.statusRecorder, m.wgIface)
|
||||
|
||||
go func() {
|
||||
if err := m.dnsForwarder.Listen(fwdEntries); err != nil { //nolint:staticcheck
|
||||
if err := m.dnsForwarder.Listen(fwdEntries); err != nil {
|
||||
// todo handle close error if it is exists
|
||||
log.Errorf("failed to start DNS forwarder, err: %v", err)
|
||||
}
|
||||
|
||||
@@ -863,40 +863,19 @@ func (e *Engine) modifyPeers(peersUpdate []*mgmProto.RemotePeerConfig) error {
|
||||
}
|
||||
}
|
||||
|
||||
// second, look up the activation state of all modified peers before removing
|
||||
// any of them, so an unavailable state leaves the current connections intact
|
||||
active := make(map[string]bool, len(modified))
|
||||
// second, close all modified connections and remove them from the state map
|
||||
for _, p := range modified {
|
||||
peerPubKey := p.GetWgPubKey()
|
||||
state, err := e.statusRecorder.GetPeer(peerPubKey)
|
||||
err := e.removePeer(p.GetWgPubKey())
|
||||
if err != nil {
|
||||
return fmt.Errorf("get status of modified peer %s: %w", peerPubKey, err)
|
||||
}
|
||||
active[peerPubKey] = state.ConnStatus != peer.StatusIdle
|
||||
}
|
||||
// then close all modified connections and remove them from the state map
|
||||
for _, p := range modified {
|
||||
if err := e.removePeer(p.GetWgPubKey()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// third, add the peer connections again, restoring each peer's activation
|
||||
// state: under lazy connections a re-added peer starts idle, but the remote
|
||||
// side of an established connection keeps its state and sends no further
|
||||
// offers, so a previously active peer left idle cannot reconnect until the
|
||||
// remote's connection expires.
|
||||
// third, add the peer connections again
|
||||
for _, p := range modified {
|
||||
if err := e.addNewPeer(p); err != nil {
|
||||
err := e.addNewPeer(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !active[p.GetWgPubKey()] {
|
||||
continue
|
||||
}
|
||||
conn, ok := e.peerStore.PeerConn(p.GetWgPubKey())
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
e.connMgr.ActivatePeer(e.ctx, conn)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -2593,7 +2572,7 @@ func (e *Engine) SetCapture(pc device.PacketCapture) error {
|
||||
}
|
||||
|
||||
afc := capture.NewAFPacketCapture(intf.Name(), sess)
|
||||
if err := afc.Start(); err != nil { //nolint:staticcheck // always errors on non-Linux builds
|
||||
if err := afc.Start(); err != nil {
|
||||
return fmt.Errorf("start AF_PACKET capture on %s: %w", intf.Name(), err)
|
||||
}
|
||||
e.afpacketCapture = afc
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"go.uber.org/mock/gomock"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/google/uuid"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
@@ -26,7 +26,6 @@ import (
|
||||
"github.com/netbirdio/netbird/client/iface/wgaddr"
|
||||
"github.com/netbirdio/netbird/client/iface/wgproxy"
|
||||
"github.com/netbirdio/netbird/client/internal/dns"
|
||||
"github.com/netbirdio/netbird/client/internal/lazyconn"
|
||||
"github.com/netbirdio/netbird/client/internal/peer"
|
||||
"github.com/netbirdio/netbird/client/internal/peer/guard"
|
||||
icemaker "github.com/netbirdio/netbird/client/internal/peer/ice"
|
||||
@@ -467,163 +466,6 @@ func TestEngine_UpdateNetworkMap(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestEngine_ModifiedPeerKeepsActivationState verifies that a peer re-added by
|
||||
// modifyPeers keeps its previous activation state under lazy connections. A
|
||||
// modified peer is removed and re-added, and a re-add defaults to idle; the
|
||||
// remote side of an established connection keeps its state and sends no further
|
||||
// offers, so a previously active peer parked idle leaves the pair unable to
|
||||
// reconnect until the remote's connection expires.
|
||||
func TestEngine_ModifiedPeerKeepsActivationState(t *testing.T) {
|
||||
key, err := wgtypes.GeneratePrivateKey()
|
||||
require.NoError(t, err)
|
||||
|
||||
ctx, cancel := context.WithCancel(CtxInitState(context.Background()))
|
||||
t.Cleanup(cancel)
|
||||
|
||||
relayMgr := relayClient.NewManager(ctx, nil, key.PublicKey().String(), iface.DefaultMTU)
|
||||
engine := NewEngine(ctx, cancel, &EngineConfig{
|
||||
WgIfaceName: "utun103",
|
||||
WgAddr: wgaddr.MustParseWGAddress("100.64.0.1/24"),
|
||||
WgPrivateKey: key,
|
||||
WgPort: 33101,
|
||||
MTU: iface.DefaultMTU,
|
||||
LazyConnection: lazyconn.StateOn,
|
||||
}, EngineServices{
|
||||
SignalClient: &signal.MockClient{},
|
||||
MgmClient: &mgmt.MockClient{},
|
||||
RelayManager: relayMgr,
|
||||
StatusRecorder: peer.NewRecorder("https://mgm"),
|
||||
}, MobileDependency{})
|
||||
|
||||
wgIface := &MockWGIface{
|
||||
NameFunc: func() string { return "utun103" },
|
||||
IsUserspaceBindFunc: func() bool {
|
||||
return false
|
||||
},
|
||||
RemovePeerFunc: func(peerKey string) error {
|
||||
return nil
|
||||
},
|
||||
AddressFunc: func() wgaddr.Address {
|
||||
return wgaddr.Address{
|
||||
IP: netip.MustParseAddr("10.20.0.1"),
|
||||
Network: netip.MustParsePrefix("10.20.0.0/24"),
|
||||
}
|
||||
},
|
||||
UpdatePeerFunc: func(peerKey string, allowedIps []netip.Prefix, keepAlive time.Duration, endpoint *net.UDPAddr, preSharedKey *wgtypes.Key) error {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
engine.wgInterface = wgIface
|
||||
engine.routeManager = routemanager.NewManager(routemanager.ManagerConfig{
|
||||
Context: ctx,
|
||||
PublicKey: key.PublicKey().String(),
|
||||
DNSRouteInterval: time.Minute,
|
||||
WGInterface: engine.wgInterface,
|
||||
StatusRecorder: engine.statusRecorder,
|
||||
RelayManager: relayMgr,
|
||||
})
|
||||
require.NoError(t, engine.routeManager.Init())
|
||||
engine.dnsServer = &dns.MockServer{
|
||||
UpdateDNSServerFunc: func(serial uint64, update nbdns.Config) error { return nil },
|
||||
}
|
||||
udpConn, err := net.ListenUDP("udp4", nil)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
if err := udpConn.Close(); err != nil {
|
||||
t.Errorf("close UDP listener: %v", err)
|
||||
}
|
||||
})
|
||||
engine.udpMux = udpmux.NewUniversalUDPMuxDefault(udpmux.UniversalUDPMuxParams{UDPConn: udpConn, MTU: 1280})
|
||||
engine.ctx = ctx
|
||||
engine.srWatcher = guard.NewSRWatcher(nil, nil, nil, icemaker.Config{})
|
||||
engine.connMgr = NewConnMgr(engine.config, engine.statusRecorder, engine.peerStore, wgIface)
|
||||
engine.connMgr.Start(ctx)
|
||||
t.Cleanup(engine.connMgr.Close)
|
||||
|
||||
// No agent version: not lazy-capable, so the connection opens permanently.
|
||||
activePeer := &mgmtProto.RemotePeerConfig{
|
||||
WgPubKey: "RRHf3Ma6z6mdLbriAJbqhX7+nM/B71lgw2+91q3LfhU=",
|
||||
AllowedIps: []string{"100.64.0.10/24"},
|
||||
}
|
||||
// Lazy-capable, never activated: managed as idle.
|
||||
idlePeer := &mgmtProto.RemotePeerConfig{
|
||||
WgPubKey: "LLHf3Ma6z6mdLbriAJbqhX7+nM/B71lgw2+91q3LfhU=",
|
||||
AllowedIps: []string{"100.64.0.11/24"},
|
||||
AgentVersion: "development",
|
||||
}
|
||||
|
||||
err = engine.updateNetworkMap(&mgmtProto.NetworkMap{
|
||||
Serial: 1,
|
||||
RemotePeers: []*mgmtProto.RemotePeerConfig{activePeer, idlePeer},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
state, err := engine.statusRecorder.GetPeer(activePeer.WgPubKey)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, peer.StatusConnecting, state.ConnStatus, "peer without lazy support should open a permanent connection")
|
||||
|
||||
state, err = engine.statusRecorder.GetPeer(idlePeer.WgPubKey)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, peer.StatusIdle, state.ConnStatus, "lazy-capable peer should be managed as idle")
|
||||
|
||||
// The active peer's agent version changes, as when a peer registered over the
|
||||
// API logs in and fills in its meta; the idle peer's allowed IPs change. Both
|
||||
// count as modified and are removed and re-added.
|
||||
err = engine.updateNetworkMap(&mgmtProto.NetworkMap{
|
||||
Serial: 2,
|
||||
RemotePeers: []*mgmtProto.RemotePeerConfig{
|
||||
{
|
||||
WgPubKey: activePeer.WgPubKey,
|
||||
AllowedIps: activePeer.AllowedIps,
|
||||
AgentVersion: "development",
|
||||
},
|
||||
{
|
||||
WgPubKey: idlePeer.WgPubKey,
|
||||
AllowedIps: []string{"100.64.0.21/24"},
|
||||
AgentVersion: "development",
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
state, err = engine.statusRecorder.GetPeer(activePeer.WgPubKey)
|
||||
require.NoError(t, err)
|
||||
assert.NotEqual(t, peer.StatusIdle, state.ConnStatus, "previously active peer should stay active after a modify")
|
||||
|
||||
state, err = engine.statusRecorder.GetPeer(idlePeer.WgPubKey)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, peer.StatusIdle, state.ConnStatus, "previously idle peer should stay idle after a modify")
|
||||
|
||||
// A missing status entry fails the modify before any connection is removed.
|
||||
require.NoError(t, engine.statusRecorder.RemovePeer(activePeer.WgPubKey))
|
||||
err = engine.updateNetworkMap(&mgmtProto.NetworkMap{
|
||||
Serial: 3,
|
||||
RemotePeers: []*mgmtProto.RemotePeerConfig{
|
||||
{
|
||||
WgPubKey: activePeer.WgPubKey,
|
||||
AllowedIps: []string{"100.64.0.30/24"},
|
||||
AgentVersion: "development",
|
||||
},
|
||||
{
|
||||
WgPubKey: idlePeer.WgPubKey,
|
||||
AllowedIps: []string{"100.64.0.31/24"},
|
||||
AgentVersion: "development",
|
||||
},
|
||||
},
|
||||
})
|
||||
require.ErrorContains(t, err, "get status of modified peer", "a modify with an unavailable peer state should fail")
|
||||
|
||||
activeConn, ok := engine.peerStore.PeerConn(activePeer.WgPubKey)
|
||||
require.True(t, ok, "peer with unavailable state should keep its connection")
|
||||
assert.True(t, compareNetIPLists(activeConn.WgConfig().AllowedIps, activePeer.AllowedIps),
|
||||
"peer with unavailable state should keep its allowed IPs")
|
||||
|
||||
idleConn, ok := engine.peerStore.PeerConn(idlePeer.WgPubKey)
|
||||
require.True(t, ok, "the other modified peer should keep its connection")
|
||||
assert.True(t, compareNetIPLists(idleConn.WgConfig().AllowedIps, []string{"100.64.0.21/24"}),
|
||||
"the other modified peer should keep its allowed IPs")
|
||||
}
|
||||
|
||||
func TestEngine_UpdateNetworkMapWithRoutes(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
|
||||
@@ -445,7 +445,7 @@ func (conn *Conn) onICEConnectionIsReady(priority conntype.ConnPriority, iceConn
|
||||
conn.dumpState.NewLocalProxy()
|
||||
wgProxy, err = conn.newProxy(iceConnInfo.RemoteConn)
|
||||
if err != nil {
|
||||
conn.Log.Errorf("failed to add relayed net.Conn to local proxy: %v", err)
|
||||
conn.Log.Errorf("failed to add turn net.Conn to local proxy: %v", err)
|
||||
return
|
||||
}
|
||||
ep = wgProxy.EndpointAddr()
|
||||
@@ -883,8 +883,9 @@ func (conn *Conn) newProxy(remoteConn net.Conn) (wgproxy.Proxy, error) {
|
||||
}
|
||||
|
||||
wgProxy := conn.config.WgConfig.WgInterface.GetProxy()
|
||||
if err := wgProxy.AddRelayedConn(conn.ctx, udpAddr, remoteConn); err != nil {
|
||||
return nil, fmt.Errorf("add relayed conn to proxy: %w", err)
|
||||
if err := wgProxy.AddTurnConn(conn.ctx, udpAddr, remoteConn); err != nil {
|
||||
conn.Log.Errorf("failed to add turn net.Conn to local proxy: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
return wgProxy, nil
|
||||
}
|
||||
|
||||
@@ -255,8 +255,8 @@ func (w *WorkerICE) connect(ctx context.Context, agent *icemaker.ThreadSafeAgent
|
||||
return
|
||||
}
|
||||
|
||||
w.log.Debugf("agent dial")
|
||||
remoteConn, err := w.agentDial(ctx, agent, remoteOfferAnswer)
|
||||
w.log.Debugf("turn agent dial")
|
||||
remoteConn, err := w.turnAgentDial(ctx, agent, remoteOfferAnswer)
|
||||
if err != nil {
|
||||
w.log.Debugf("failed to dial the remote peer: %s", err)
|
||||
w.closeAgent(agent, w.agentDialerCancel)
|
||||
@@ -517,8 +517,8 @@ func (w *WorkerICE) onConnectionStateChange(agent *icemaker.ThreadSafeAgent, dia
|
||||
w.logSuccessfulPaths(agent)
|
||||
return
|
||||
case ice.ConnectionStateFailed, ice.ConnectionStateDisconnected, ice.ConnectionStateClosed:
|
||||
// ice.ConnectionStateClosed happens when we recreate the agent. The P2P to relay switch requires
|
||||
// notifying conn.onICEStateDisconnected so it can update the currently used priority.
|
||||
// ice.ConnectionStateClosed happens when we recreate the agent. For the P2P to TURN switch important to
|
||||
// notify the conn.onICEStateDisconnected changes to update the current used priority
|
||||
|
||||
sessionChanged := w.closeAgent(agent, dialerCancel)
|
||||
|
||||
@@ -532,7 +532,7 @@ func (w *WorkerICE) onConnectionStateChange(agent *icemaker.ThreadSafeAgent, dia
|
||||
}
|
||||
}
|
||||
|
||||
func (w *WorkerICE) agentDial(ctx context.Context, agent *icemaker.ThreadSafeAgent, remoteOfferAnswer *OfferAnswer) (*ice.Conn, error) {
|
||||
func (w *WorkerICE) turnAgentDial(ctx context.Context, agent *icemaker.ThreadSafeAgent, remoteOfferAnswer *OfferAnswer) (*ice.Conn, error) {
|
||||
if isController(w.config) {
|
||||
return agent.Dial(ctx, remoteOfferAnswer.IceCredentials.UFrag, remoteOfferAnswer.IceCredentials.Pwd)
|
||||
} else {
|
||||
|
||||
@@ -18,8 +18,8 @@ type Service struct {
|
||||
}
|
||||
|
||||
func New() (*Service, error) {
|
||||
d, err := NewDetector() //nolint:staticcheck
|
||||
if err != nil { //nolint:staticcheck // always errors on platforms without a sleep detector
|
||||
d, err := NewDetector()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
||||
@@ -37,32 +37,23 @@
|
||||
// Updater Process (Setup):
|
||||
//
|
||||
// 1. Receives parameters from service via command-line arguments
|
||||
// 2. Terminates the UI so the installer does not have to replace a locked image
|
||||
// file, which would otherwise leave the install needing a reboot
|
||||
// 3. Runs installer with appropriate silent/quiet flags:
|
||||
// 2. Runs installer with appropriate silent/quiet flags:
|
||||
// - Windows EXE: installer.exe /S
|
||||
// - Windows MSI: msiexec.exe /i installer.msi /qn /norestart REBOOT=ReallySuppress /l*v msi.log
|
||||
// - Windows MSI: msiexec.exe /i installer.msi /quiet /qn /l*v msi.log
|
||||
// - macOS PKG: installer -pkg installer.pkg -target /
|
||||
// - macOS Homebrew: brew upgrade netbirdio/tap/netbird
|
||||
// 4. Installer terminates the daemon
|
||||
// 5. Installer replaces binaries with new version
|
||||
// 6. Updater waits for installer to complete. On Windows, MSI exit codes 3010
|
||||
// (ERROR_SUCCESS_REBOOT_REQUIRED) and 1641 (ERROR_SUCCESS_REBOOT_INITIATED)
|
||||
// are a pending-reboot outcome, not a failure: the install succeeded, but
|
||||
// some files are only replaced on the next restart (the reboot itself is
|
||||
// suppressed via /norestart and REBOOT=ReallySuppress), and the flow
|
||||
// continues as on success
|
||||
// 7. Updater restarts daemon:
|
||||
// 3. Installer terminates daemon and UI processes
|
||||
// 4. Installer replaces binaries with new version
|
||||
// 5. Updater waits for installer to complete
|
||||
// 6. Updater restarts daemon:
|
||||
// - Windows: netbird.exe service start
|
||||
// - macOS/Linux: netbird service start
|
||||
// 8. Updater restarts UI:
|
||||
// - Windows: Launches netbird-ui.exe using CreateProcessAsUser in every
|
||||
// session it was terminated in, falling back to the active console session
|
||||
// 7. Updater restarts UI:
|
||||
// - Windows: Launches netbird-ui.exe as active console user using CreateProcessAsUser
|
||||
// - macOS: Uses launchctl asuser to launch NetBird.app for console user
|
||||
// - Linux: Not implemented (UI typically auto-starts)
|
||||
// 9. Updater writes result.json with success/error status (a pending reboot is
|
||||
// recorded as success)
|
||||
// 10. Updater process exits
|
||||
// 8. Updater writes result.json with success/error status
|
||||
// 9. Updater process exits
|
||||
//
|
||||
// # Result Communication
|
||||
//
|
||||
|
||||
@@ -2,7 +2,6 @@ package installer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
@@ -23,12 +22,6 @@ const (
|
||||
|
||||
msiLogFile = "msi.log"
|
||||
|
||||
// ERROR_SUCCESS_REBOOT_REQUIRED and ERROR_SUCCESS_REBOOT_INITIATED
|
||||
msiRebootRequired = 3010
|
||||
msiRebootInitiated = 1641
|
||||
|
||||
processExitWait = 10 * time.Second
|
||||
|
||||
msiDownloadURL = "https://github.com/netbirdio/netbird/releases/download/v%version/netbird_installer_%version_windows_%arch.msi"
|
||||
exeDownloadURL = "https://github.com/netbirdio/netbird/releases/download/v%version/netbird_installer_%version_windows_%arch.exe"
|
||||
)
|
||||
@@ -45,8 +38,6 @@ var (
|
||||
func (u *Installer) Setup(ctx context.Context, dryRun bool, installerFile string, daemonFolder string) (resultErr error) {
|
||||
resultHandler := NewResultHandler(u.tempDir)
|
||||
|
||||
var uiSessions []uint32
|
||||
|
||||
// Always ensure daemon and UI are restarted after setup
|
||||
defer func() {
|
||||
log.Infof("starting daemon back")
|
||||
@@ -55,7 +46,7 @@ func (u *Installer) Setup(ctx context.Context, dryRun bool, installerFile string
|
||||
}
|
||||
|
||||
log.Infof("starting UI back")
|
||||
if err := u.startUI(daemonFolder, uiSessions); err != nil {
|
||||
if err := u.startUIAsUser(daemonFolder); err != nil {
|
||||
log.Errorf("failed to start UI: %v", err)
|
||||
}
|
||||
|
||||
@@ -84,14 +75,6 @@ func (u *Installer) Setup(ctx context.Context, dryRun bool, installerFile string
|
||||
return
|
||||
}
|
||||
|
||||
// The UI holds an open handle on its own image. Left running, Restart Manager
|
||||
// cannot shut it down (msiexec runs as LocalSystem here, the UI as the
|
||||
// interactive user), so the MSI falls back to replacing the file on reboot and
|
||||
// marks the install as restart-required. The deferred close-application action
|
||||
// in the package runs too late to prevent that, it happens after
|
||||
// InstallValidate has already registered the file as in use.
|
||||
uiSessions = killUI()
|
||||
|
||||
var cmd *exec.Cmd
|
||||
switch installerType {
|
||||
case TypeExe:
|
||||
@@ -101,9 +84,7 @@ func (u *Installer) Setup(ctx context.Context, dryRun bool, installerFile string
|
||||
installerDir := filepath.Dir(installerFile)
|
||||
logPath := filepath.Join(installerDir, msiLogFile)
|
||||
log.Infof("run msi installer: %s", installerFile)
|
||||
// REBOOT=ReallySuppress: a silent install has no way to ask, so without it
|
||||
// msiexec reboots the machine on its own if it decides one is needed.
|
||||
cmd = exec.CommandContext(ctx, "msiexec.exe", "/i", filepath.Base(installerFile), "/qn", "/norestart", "REBOOT=ReallySuppress", "/l*v", logPath)
|
||||
cmd = exec.CommandContext(ctx, "msiexec.exe", "/i", filepath.Base(installerFile), "/quiet", "/qn", "/l*v", logPath)
|
||||
}
|
||||
|
||||
cmd.Dir = filepath.Dir(installerFile)
|
||||
@@ -114,13 +95,9 @@ func (u *Installer) Setup(ctx context.Context, dryRun bool, installerFile string
|
||||
}
|
||||
|
||||
log.Infof("installer started with PID %d", cmd.Process.Pid)
|
||||
if err := cmd.Wait(); err != nil {
|
||||
if !isRebootPending(err) {
|
||||
resultErr = err
|
||||
log.Errorf("installer process finished with error: %v", err)
|
||||
return
|
||||
}
|
||||
log.Warnf("installer completed but reported a pending reboot, some files will be replaced on the next restart")
|
||||
if resultErr = cmd.Wait(); resultErr != nil {
|
||||
log.Errorf("installer process finished with error: %v", resultErr)
|
||||
return
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -140,142 +117,16 @@ func (u *Installer) startDaemon(daemonFolder string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *Installer) startUI(daemonFolder string, sessionIDs []uint32) error {
|
||||
func (u *Installer) startUIAsUser(daemonFolder string) error {
|
||||
uiPath := filepath.Join(daemonFolder, uiName)
|
||||
log.Infof("starting netbird-ui: %s", uiPath)
|
||||
|
||||
if len(sessionIDs) == 0 {
|
||||
sessionID := windows.WTSGetActiveConsoleSessionId()
|
||||
if sessionID == 0xFFFFFFFF {
|
||||
return fmt.Errorf("no active user session found")
|
||||
}
|
||||
sessionIDs = []uint32{sessionID}
|
||||
// Get the active console session ID
|
||||
sessionID := windows.WTSGetActiveConsoleSessionId()
|
||||
if sessionID == 0xFFFFFFFF {
|
||||
return fmt.Errorf("no active user session found")
|
||||
}
|
||||
|
||||
var errs []error
|
||||
for _, sessionID := range sessionIDs {
|
||||
if err := startUIInSession(uiPath, sessionID); err != nil {
|
||||
errs = append(errs, fmt.Errorf("session %d: %w", sessionID, err))
|
||||
continue
|
||||
}
|
||||
log.Infof("netbird-ui started successfully in session %d", sessionID)
|
||||
}
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
// isRebootPending reports whether the installer exit code means it succeeded but
|
||||
// left work for the next restart. The reboot itself is suppressed, so this is not
|
||||
// a failure.
|
||||
func isRebootPending(err error) bool {
|
||||
var exitErr *exec.ExitError
|
||||
if !errors.As(err, &exitErr) {
|
||||
return false
|
||||
}
|
||||
|
||||
switch exitErr.ExitCode() {
|
||||
case msiRebootRequired, msiRebootInitiated:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// killUI terminates any running netbird-ui process and returns the IDs of the
|
||||
// interactive sessions the terminated processes belonged to. Setup starts the
|
||||
// UI again in those sessions once the installer is done.
|
||||
func killUI() []uint32 {
|
||||
pids, err := processIDsByName(uiName)
|
||||
if err != nil {
|
||||
log.Warnf("failed to look up %s processes: %v", uiName, err)
|
||||
return nil
|
||||
}
|
||||
|
||||
sessions := make(map[uint32]struct{})
|
||||
for _, pid := range pids {
|
||||
var sessionID uint32
|
||||
if err := windows.ProcessIdToSessionId(pid, &sessionID); err != nil {
|
||||
log.Warnf("failed to look up session of %s (PID %d): %v", uiName, pid, err)
|
||||
}
|
||||
|
||||
if err := terminateProcess(pid); err != nil {
|
||||
log.Warnf("failed to terminate %s (PID %d): %v", uiName, pid, err)
|
||||
continue
|
||||
}
|
||||
log.Infof("terminated %s (PID %d) in session %d", uiName, pid, sessionID)
|
||||
|
||||
if sessionID != 0 {
|
||||
sessions[sessionID] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
sessionIDs := make([]uint32, 0, len(sessions))
|
||||
for sessionID := range sessions {
|
||||
sessionIDs = append(sessionIDs, sessionID)
|
||||
}
|
||||
return sessionIDs
|
||||
}
|
||||
|
||||
func processIDsByName(name string) ([]uint32, error) {
|
||||
snapshot, err := windows.CreateToolhelp32Snapshot(windows.TH32CS_SNAPPROCESS, 0)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("create process snapshot: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
if err := windows.CloseHandle(snapshot); err != nil {
|
||||
log.Warnf("failed to close process snapshot: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
var entry windows.ProcessEntry32
|
||||
entry.Size = uint32(unsafe.Sizeof(entry))
|
||||
|
||||
var pids []uint32
|
||||
for err = windows.Process32First(snapshot, &entry); err == nil; err = windows.Process32Next(snapshot, &entry) {
|
||||
if strings.EqualFold(windows.UTF16ToString(entry.ExeFile[:]), name) {
|
||||
pids = append(pids, entry.ProcessID)
|
||||
}
|
||||
}
|
||||
if !errors.Is(err, windows.ERROR_NO_MORE_FILES) {
|
||||
return nil, fmt.Errorf("enumerate processes: %w", err)
|
||||
}
|
||||
|
||||
return pids, nil
|
||||
}
|
||||
|
||||
func terminateProcess(pid uint32) error {
|
||||
handle, err := windows.OpenProcess(windows.PROCESS_TERMINATE|windows.SYNCHRONIZE, false, pid)
|
||||
if err != nil {
|
||||
// The process may have exited between enumeration and now.
|
||||
if errors.Is(err, windows.ERROR_INVALID_PARAMETER) {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("open process: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
if err := windows.CloseHandle(handle); err != nil {
|
||||
log.Warnf("failed to close process handle: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
if err := windows.TerminateProcess(handle, 0); err != nil {
|
||||
return fmt.Errorf("terminate process: %w", err)
|
||||
}
|
||||
|
||||
// Wait for the handle to signal so the image file is released before the
|
||||
// installer tries to overwrite it. A timeout is reported through the returned
|
||||
// event, not through err, which stays nil unless the wait itself failed.
|
||||
event, err := windows.WaitForSingleObject(handle, uint32(processExitWait.Milliseconds()))
|
||||
if err != nil {
|
||||
return fmt.Errorf("wait for process exit: %w", err)
|
||||
}
|
||||
if event != windows.WAIT_OBJECT_0 {
|
||||
return fmt.Errorf("wait for process exit: unexpected wait result %#x", event)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func startUIInSession(uiPath string, sessionID uint32) error {
|
||||
// Get the user token for that session
|
||||
var userToken windows.Token
|
||||
err := windows.WTSQueryUserToken(sessionID, &userToken)
|
||||
@@ -307,16 +158,6 @@ func startUIInSession(uiPath string, sessionID uint32) error {
|
||||
}
|
||||
}()
|
||||
|
||||
var env *uint16
|
||||
if err := windows.CreateEnvironmentBlock(&env, primaryToken, false); err != nil {
|
||||
return fmt.Errorf("create environment block: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
if err := windows.DestroyEnvironmentBlock(env); err != nil {
|
||||
log.Warnf("failed to destroy environment block: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Prepare startup info
|
||||
var si windows.StartupInfo
|
||||
si.Cb = uint32(unsafe.Sizeof(si))
|
||||
@@ -339,7 +180,7 @@ func startUIInSession(uiPath string, sessionID uint32) error {
|
||||
nil,
|
||||
false,
|
||||
creationFlags,
|
||||
env,
|
||||
nil,
|
||||
nil,
|
||||
&si,
|
||||
&pi,
|
||||
@@ -356,6 +197,7 @@ func startUIInSession(uiPath string, sessionID uint32) error {
|
||||
log.Warnf("failed to close thread handle: %v", err)
|
||||
}
|
||||
|
||||
log.Infof("netbird-ui started successfully in session %d", sessionID)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -1,108 +0,0 @@
|
||||
package installer
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os/exec"
|
||||
"slices"
|
||||
"strconv"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// exitErrorWithCode returns a real *exec.ExitError carrying the given exit code.
|
||||
func exitErrorWithCode(t *testing.T, code int) error {
|
||||
t.Helper()
|
||||
|
||||
err := exec.Command("cmd.exe", "/c", "exit "+strconv.Itoa(code)).Run()
|
||||
if err == nil {
|
||||
t.Fatalf("expected a non-zero exit for code %d", code)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func TestIsRebootPending(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
code int
|
||||
want bool
|
||||
}{
|
||||
{name: "reboot required", code: msiRebootRequired, want: true},
|
||||
{name: "reboot initiated", code: msiRebootInitiated, want: true},
|
||||
{name: "generic failure", code: 1603, want: false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := isRebootPending(exitErrorWithCode(t, tt.code)); got != tt.want {
|
||||
t.Errorf("isRebootPending(exit %d) = %v, want %v", tt.code, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestProcessIDsByNameAndTerminate spawns a long-running system process, finds it
|
||||
// by name and terminates it, covering the path the updater uses to release the UI
|
||||
// image file before the installer replaces it.
|
||||
func TestProcessIDsByNameAndTerminate(t *testing.T) {
|
||||
cmd := exec.Command("ping.exe", "-n", "60", "127.0.0.1")
|
||||
if err := cmd.Start(); err != nil {
|
||||
t.Fatalf("start ping: %v", err)
|
||||
}
|
||||
|
||||
pid := uint32(cmd.Process.Pid)
|
||||
killed := false
|
||||
t.Cleanup(func() {
|
||||
if !killed {
|
||||
_ = cmd.Process.Kill()
|
||||
}
|
||||
_ = cmd.Wait()
|
||||
})
|
||||
|
||||
// Name matching must be case-insensitive: the snapshot reports PING.EXE.
|
||||
pids, err := processIDsByName("ping.exe")
|
||||
if err != nil {
|
||||
t.Fatalf("processIDsByName: %v", err)
|
||||
}
|
||||
|
||||
if !slices.Contains(pids, pid) {
|
||||
t.Fatalf("PID %d not among the ping.exe processes found: %v", pid, pids)
|
||||
}
|
||||
|
||||
if err := terminateProcess(pid); err != nil {
|
||||
t.Fatalf("terminateProcess: %v", err)
|
||||
}
|
||||
killed = true
|
||||
|
||||
// terminateProcess only returns once the handle has signalled, so the process
|
||||
// is already gone and Wait must not block. It exits with the code passed to
|
||||
// TerminateProcess, which is 0, so Wait reports no error.
|
||||
if err := cmd.Wait(); err != nil {
|
||||
t.Fatalf("wait for terminated ping: %v", err)
|
||||
}
|
||||
if !cmd.ProcessState.Exited() {
|
||||
t.Error("process did not exit after terminateProcess")
|
||||
}
|
||||
|
||||
remaining, err := processIDsByName("ping.exe")
|
||||
if err != nil {
|
||||
t.Fatalf("processIDsByName after terminate: %v", err)
|
||||
}
|
||||
if slices.Contains(remaining, pid) {
|
||||
t.Errorf("PID %d still listed after terminateProcess", pid)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProcessIDsByNameNoMatch(t *testing.T) {
|
||||
pids, err := processIDsByName("netbird-nonexistent-process.exe")
|
||||
if err != nil {
|
||||
t.Fatalf("processIDsByName: %v", err)
|
||||
}
|
||||
if len(pids) != 0 {
|
||||
t.Errorf("expected no matches, got %v", pids)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsRebootPendingNonExitError(t *testing.T) {
|
||||
if isRebootPending(errors.New("start installer: file not found")) {
|
||||
t.Error("a non-exit error must not be treated as a pending reboot")
|
||||
}
|
||||
}
|
||||
@@ -435,7 +435,7 @@ func (m *Manager) install(ctx context.Context, pendingVersion *v.Version) error
|
||||
}
|
||||
|
||||
inst := installer.New()
|
||||
if err := inst.RunInstallation(ctx, pendingVersion.String()); err != nil { //nolint:staticcheck // always errors on platforms without an installer
|
||||
if err := inst.RunInstallation(ctx, pendingVersion.String()); err != nil {
|
||||
log.Errorf("error triggering update: %v", err)
|
||||
m.statusRecorder.PublishEvent(
|
||||
cProto.SystemEvent_ERROR,
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
@@ -70,7 +69,7 @@ func setStdHandle(f *os.File) error {
|
||||
handle := f.Fd()
|
||||
r0, _, e1 := setStdHandleFn.Call(stdErrorHandle, handle)
|
||||
if r0 == 0 {
|
||||
if !errors.Is(e1, syscall.Errno(0)) {
|
||||
if e1 != nil {
|
||||
return e1
|
||||
}
|
||||
return syscall.EINVAL
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"go.uber.org/mock/gomock"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.opentelemetry.io/otel"
|
||||
|
||||
|
||||
@@ -75,8 +75,8 @@ func (s *Server) createCommand(logger *log.Entry, privilegeResult PrivilegeCheck
|
||||
}
|
||||
|
||||
// Try su first for system integration (PAM/audit) when privileged
|
||||
cmd, err := s.createSuCommand(logger, session, localUser, hasPty) //nolint:staticcheck
|
||||
if err != nil || privilegeResult.UsedFallback { //nolint:staticcheck // always errors on platforms without su
|
||||
cmd, err := s.createSuCommand(logger, session, localUser, hasPty)
|
||||
if err != nil || privilegeResult.UsedFallback {
|
||||
logger.Debugf("su command failed, falling back to executor: %v", err)
|
||||
cmd, cleanup, err := s.createExecutorCommand(logger, session, localUser, hasPty)
|
||||
if err != nil {
|
||||
|
||||
@@ -30,11 +30,6 @@ func GetInfo(ctx context.Context) *Info {
|
||||
kernelVersion = osInfo[2]
|
||||
}
|
||||
|
||||
addrs, err := networkAddresses()
|
||||
if err != nil {
|
||||
log.Warnf("discover network addresses: %s", err)
|
||||
}
|
||||
|
||||
gio := &Info{
|
||||
GoOS: runtime.GOOS,
|
||||
Kernel: kernel,
|
||||
@@ -46,7 +41,6 @@ func GetInfo(ctx context.Context) *Info {
|
||||
NetbirdVersion: version.NetbirdVersion(),
|
||||
UIVersion: extractUIVersion(ctx),
|
||||
KernelVersion: kernelVersion,
|
||||
NetworkAddresses: addrs,
|
||||
SystemSerialNumber: serial(),
|
||||
SystemProductName: productModel(),
|
||||
SystemManufacturer: productManufacturer(),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build !ios && !android
|
||||
//go:build !ios
|
||||
|
||||
package system
|
||||
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var iFaceDiscover IFaceDiscover
|
||||
|
||||
type IFaceDiscover interface {
|
||||
IFaces() (string, error)
|
||||
}
|
||||
|
||||
// SetIFaceDiscover configures the Android interface discovery provider.
|
||||
func SetIFaceDiscover(discover IFaceDiscover) {
|
||||
iFaceDiscover = discover
|
||||
}
|
||||
|
||||
func networkAddresses() ([]NetworkAddress, error) {
|
||||
if iFaceDiscover == nil {
|
||||
return nil, nil
|
||||
}
|
||||
ifaces, err := iFaceDiscover.IFaces()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var netAddresses []NetworkAddress
|
||||
for _, line := range strings.Split(ifaces, "\n") {
|
||||
addresses, ok := interfaceAddresses(line)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
for _, address := range addresses {
|
||||
netAddr, ok := toNetworkAddress(address)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if isDuplicated(netAddresses, netAddr) {
|
||||
continue
|
||||
}
|
||||
netAddresses = append(netAddresses, netAddr)
|
||||
}
|
||||
}
|
||||
return netAddresses, nil
|
||||
}
|
||||
|
||||
func interfaceAddresses(line string) ([]string, bool) {
|
||||
parts := strings.Split(line, "|")
|
||||
if len(parts) != 2 {
|
||||
return nil, false
|
||||
}
|
||||
flags := strings.Fields(parts[0])
|
||||
if len(flags) != 8 {
|
||||
return nil, false
|
||||
}
|
||||
up, loopback := flags[3], flags[5]
|
||||
if up != "true" || loopback == "true" {
|
||||
return nil, false
|
||||
}
|
||||
return strings.Fields(parts[1]), true
|
||||
}
|
||||
|
||||
func toNetworkAddress(address string) (NetworkAddress, bool) {
|
||||
prefix, err := netip.ParsePrefix(address)
|
||||
if err != nil {
|
||||
return NetworkAddress{}, false
|
||||
}
|
||||
if prefix.Addr().Is4In6() {
|
||||
if prefix.Bits() < 96 {
|
||||
return NetworkAddress{}, false
|
||||
}
|
||||
prefix = netip.PrefixFrom(prefix.Addr().Unmap(), prefix.Bits()-96)
|
||||
}
|
||||
ip := prefix.Addr()
|
||||
if ip.IsLoopback() || ip.IsLinkLocalUnicast() || ip.IsMulticast() {
|
||||
return NetworkAddress{}, false
|
||||
}
|
||||
return NetworkAddress{NetIP: prefix}, true
|
||||
}
|
||||
|
||||
func isDuplicated(addresses []NetworkAddress, addr NetworkAddress) bool {
|
||||
for _, duplicated := range addresses {
|
||||
if duplicated.NetIP == addr.NetIP {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build !ios && !android
|
||||
//go:build !ios
|
||||
|
||||
package system
|
||||
|
||||
|
||||
@@ -146,14 +146,11 @@ func (c *GRPCClient) Receive(ctx context.Context, interval time.Duration, msgHan
|
||||
|
||||
streamStart := time.Now()
|
||||
|
||||
// receive always returns a non-nil error once the stream breaks;
|
||||
// handleRetryableError decides between reconnecting and exiting
|
||||
// permanently on local context cancellation
|
||||
err = c.receive(stream, msgHandler)
|
||||
if !isContextDone(err) {
|
||||
if err := c.receive(stream, msgHandler); err != nil {
|
||||
log.Errorf("receive failed: %v", err)
|
||||
return c.handleRetryableError(err, streamStart, backOff)
|
||||
}
|
||||
return c.handleRetryableError(err, streamStart, backOff)
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := backoff.Retry(operation, backOff); err != nil {
|
||||
|
||||
4
go.mod
4
go.mod
@@ -62,6 +62,7 @@ require (
|
||||
github.com/goccy/go-yaml v1.18.0
|
||||
github.com/godbus/dbus/v5 v5.2.2
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1
|
||||
github.com/golang/mock v1.6.0
|
||||
github.com/google/go-cmp v0.7.0
|
||||
github.com/google/gopacket v1.1.19
|
||||
github.com/google/nftables v0.3.0
|
||||
@@ -216,7 +217,6 @@ require (
|
||||
github.com/gobwas/pool v0.2.1 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
|
||||
github.com/golang/mock v1.6.0 // indirect
|
||||
github.com/google/btree v1.1.3 // indirect
|
||||
github.com/google/go-querystring v1.1.0 // indirect
|
||||
github.com/google/go-tpm v0.9.8 // indirect
|
||||
@@ -340,5 +340,3 @@ replace github.com/dexidp/dex/api/v2 => github.com/netbirdio/dex/api/v2 v2.0.0-2
|
||||
replace github.com/mailru/easyjson => github.com/netbirdio/easyjson v0.9.0
|
||||
|
||||
replace github.com/wailsapp/wails/v3 => github.com/netbirdio/wails/v3 v3.0.0-beta.3.0.20260810103952-24e716aea4db
|
||||
|
||||
tool go.uber.org/mock/mockgen
|
||||
|
||||
@@ -15,12 +15,6 @@ set -o pipefail
|
||||
# 2. Postgres migration — add Postgres, migrate SQLite data via migrate-store.
|
||||
# 3. Traffic flow — add NATS + flow-enricher + flow-receiver.
|
||||
#
|
||||
# Step 2 is skipped when the deployment already runs on Postgres
|
||||
# (server.store.engine: postgres in config.yaml). Nothing is provisioned or
|
||||
# migrated in that case and the store config is left exactly as the operator
|
||||
# wrote it — the enterprise image reads the same Postgres the community image
|
||||
# did. Such a deployment gets the image swap, and can still opt into step 3.
|
||||
#
|
||||
# If any step fails once the stack has been touched, the script rolls itself
|
||||
# back automatically: generated files are removed, the Postgres volume this run
|
||||
# created is dropped, and the original deployment is started again.
|
||||
@@ -44,18 +38,6 @@ ENV_BACKUP=""
|
||||
PG_VOLUME_NAME=""
|
||||
BACKUP_DIR=""
|
||||
|
||||
# Store state. STORE_ENGINE is what the deployment runs on today; when it is
|
||||
# already postgres, MIGRATE_POSTGRES stays "no" and nothing is provisioned.
|
||||
# POSTGRES_SERVICE is empty when Postgres lives outside this compose project.
|
||||
STORE_ENGINE=""
|
||||
EXISTING_POSTGRES="no"
|
||||
POSTGRES_DSN=""
|
||||
POSTGRES_SERVICE=""
|
||||
POSTGRES_DEPENDS_CONDITION="service_healthy"
|
||||
# Whether this run needs to generate config.yaml.enterprise at all. A pure
|
||||
# image swap does not.
|
||||
ENTERPRISE_CONFIG="no"
|
||||
|
||||
NETBIRD_EULA_URL="https://netbird.io/self-hosted-EULA"
|
||||
|
||||
check_docker_compose() {
|
||||
@@ -210,85 +192,6 @@ detect_exposed_address() {
|
||||
yq eval '.server.exposedAddress // ""' "$CONFIG_YAML_HOST"
|
||||
}
|
||||
|
||||
# The engine is a config.yaml-only setting — there is no env override for it
|
||||
# (combined/cmd/root.go reads it from YAML and derives the env vars), so
|
||||
# config.yaml is authoritative. Absent means the sqlite default.
|
||||
detect_store_engine() {
|
||||
local engine
|
||||
engine=$(yq eval '.server.store.engine // ""' "$CONFIG_YAML_HOST")
|
||||
if [[ -z "$engine" ]] || [[ "$engine" == "null" ]]; then
|
||||
engine="sqlite"
|
||||
fi
|
||||
echo "$engine" | tr '[:upper:]' '[:lower:]'
|
||||
}
|
||||
|
||||
detect_store_dsn() {
|
||||
yq eval '.server.store.dsn // ""' "$CONFIG_YAML_HOST"
|
||||
}
|
||||
|
||||
# config.yaml is where a combined deployment carries its DSN; this only covers
|
||||
# hand-rolled installs that keep it in the environment instead.
|
||||
detect_store_dsn_from_compose() {
|
||||
# `compose config` re-escapes a literal $ as $$ on the way out, so undo that
|
||||
# to get the value the container actually receives.
|
||||
$DOCKER_COMPOSE_COMMAND config 2>/dev/null | yq eval "
|
||||
.services[\"$COMBINED_SERVICE\"].environment.NB_STORE_ENGINE_POSTGRES_DSN //
|
||||
.services[\"$COMBINED_SERVICE\"].environment.NETBIRD_STORE_ENGINE_POSTGRES_DSN // \"\"
|
||||
" - 2>/dev/null | sed 's/\$\$/$/g'
|
||||
}
|
||||
|
||||
# Reads either DSN form: "host=db ..." or "postgres://user:pass@db:5432/name".
|
||||
dsn_host() {
|
||||
local dsn="$1"
|
||||
case "$dsn" in
|
||||
*://*) printf '%s' "$dsn" | sed -n 's,^[a-zA-Z+]*://\([^/?]*\).*,\1,p' | sed -e 's,.*@,,' -e 's,:.*,,' ;;
|
||||
*) printf '%s' "$dsn" | sed -n 's/.*[[:space:]]*host=\([^[:space:]]*\).*/\1/p' ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# flow-enricher is its own container, so a loopback host or a socket path would
|
||||
# reach the enricher rather than Postgres. Only flag hosts we can positively
|
||||
# identify — an unparseable DSN must not leave the operator with no way forward.
|
||||
dsn_host_reachable() {
|
||||
local dsn="$1"
|
||||
case "$(dsn_host "$dsn")" in
|
||||
localhost | 127.* | ::1 | 0.0.0.0 | /*) return 1 ;;
|
||||
*) return 0 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Names the compose service running this deployment's Postgres, for depends_on.
|
||||
# Empty means external — the DSN host matched no service. A DSN with no readable
|
||||
# host falls back to matching on image.
|
||||
detect_postgres_service() {
|
||||
local host
|
||||
host=$(dsn_host "$POSTGRES_DSN")
|
||||
if [[ -n "$host" ]]; then
|
||||
if [[ "$(host="$host" yq eval '.services | has(env(host))' "$COMPOSE_FILE" 2>/dev/null)" == "true" ]]; then
|
||||
echo "$host"
|
||||
fi
|
||||
return
|
||||
fi
|
||||
yq eval '.services | to_entries | map(select(.value.image // "" | test("(^|/)(postgres|postgis|pgvector|timescaledb)(:|@|$)"))) | .[0].key // ""' "$COMPOSE_FILE"
|
||||
}
|
||||
|
||||
# depends_on: service_healthy is only legal if the service defines a healthcheck.
|
||||
detect_postgres_depends_condition() {
|
||||
local tag
|
||||
tag=$(yq eval ".services[\"$POSTGRES_SERVICE\"].healthcheck | tag" "$COMPOSE_FILE" 2>/dev/null)
|
||||
if [[ "$tag" == "!!map" ]]; then
|
||||
echo "service_healthy"
|
||||
else
|
||||
echo "service_started"
|
||||
fi
|
||||
}
|
||||
|
||||
env_value() {
|
||||
local value="$1"
|
||||
value=$(printf '%s' "$value" | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g' -e 's/\$/$$/g')
|
||||
printf '"%s"' "$value"
|
||||
}
|
||||
|
||||
detect_compose_network() {
|
||||
local tag
|
||||
tag=$(yq eval ".services[\"$COMBINED_SERVICE\"].networks | tag" "$COMPOSE_FILE" 2>/dev/null)
|
||||
@@ -325,30 +228,16 @@ services:
|
||||
NETBIRD_LICENSE_SERVER_BASE_URL: \${NETBIRD_LICENSE_SERVER_BASE_URL}
|
||||
EOF
|
||||
|
||||
# An existing Postgres is already wired up by the operator's own compose file,
|
||||
# so only a Postgres this run creates needs a depends_on.
|
||||
if [[ "$MIGRATE_POSTGRES" == "yes" ]]; then
|
||||
cat <<EOF
|
||||
depends_on:
|
||||
${POSTGRES_SERVICE}:
|
||||
condition: ${POSTGRES_DEPENDS_CONDITION}
|
||||
EOF
|
||||
fi
|
||||
|
||||
# The server is only pointed at a different config file when this run
|
||||
# generates one. A pure image swap leaves it on its original config.yaml.
|
||||
if [[ "$ENTERPRISE_CONFIG" == "yes" ]]; then
|
||||
cat <<EOF
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
volumes:
|
||||
- ./${ENTERPRISE_CONFIG_FILE}:/etc/netbird/config.yaml.enterprise:ro
|
||||
command: ["--config", "/etc/netbird/config.yaml.enterprise"]
|
||||
EOF
|
||||
fi
|
||||
|
||||
if [[ "$MIGRATE_POSTGRES" == "yes" ]]; then
|
||||
cat <<EOF
|
||||
|
||||
${POSTGRES_SERVICE}:
|
||||
postgres:
|
||||
image: postgres:17
|
||||
container_name: netbird-postgres
|
||||
restart: unless-stopped
|
||||
@@ -368,14 +257,6 @@ EOF
|
||||
fi
|
||||
|
||||
if [[ "$ENABLE_FLOW" == "yes" ]]; then
|
||||
# Nothing to wait on when Postgres is managed outside this compose project.
|
||||
local enricher_depends=""
|
||||
if [[ -n "$POSTGRES_SERVICE" ]]; then
|
||||
enricher_depends="
|
||||
${POSTGRES_SERVICE}:
|
||||
condition: ${POSTGRES_DEPENDS_CONDITION}"
|
||||
fi
|
||||
|
||||
cat <<EOF
|
||||
|
||||
nats:
|
||||
@@ -392,7 +273,9 @@ EOF
|
||||
container_name: netbird-flow-enricher
|
||||
restart: unless-stopped
|
||||
networks: [${COMPOSE_NETWORK}]
|
||||
depends_on:${enricher_depends}
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
nats:
|
||||
condition: service_started
|
||||
environment:
|
||||
@@ -400,10 +283,10 @@ EOF
|
||||
NETBIRD_LICENSE_SERVER_BASE_URL: \${NETBIRD_LICENSE_SERVER_BASE_URL}
|
||||
NB_DATADIR: /var/lib/netbird
|
||||
NB_MANAGEMENT_STORE_ENGINE: postgres
|
||||
NB_MANAGEMENT_POSTGRES_DSN: \${NB_ENTERPRISE_POSTGRES_DSN}
|
||||
NB_STORE_ENGINE_POSTGRES_DSN: \${NB_ENTERPRISE_POSTGRES_DSN}
|
||||
NB_MANAGEMENT_POSTGRES_DSN: "host=postgres user=netbird password=\${POSTGRES_PASSWORD} dbname=netbird port=5432 sslmode=disable"
|
||||
NB_STORE_ENGINE_POSTGRES_DSN: "host=postgres user=netbird password=\${POSTGRES_PASSWORD} dbname=netbird port=5432 sslmode=disable"
|
||||
NB_TRAFFIC_EVENT_STORE_ENGINE: postgres
|
||||
NB_TRAFFIC_EVENT_POSTGRES_DSN: \${NB_ENTERPRISE_POSTGRES_DSN}
|
||||
NB_TRAFFIC_EVENT_POSTGRES_DSN: "host=postgres user=netbird password=\${POSTGRES_PASSWORD} dbname=netbird port=5432 sslmode=disable"
|
||||
NB_MANAGEMENT_STORE_KEY: \${NETBIRD_ENCRYPTION_KEY}
|
||||
NB_FLOW_ADAPTER_TYPE: nats
|
||||
NB_FLOW_NATS_ENDPOINTS: nats://nats:4222
|
||||
@@ -460,41 +343,27 @@ EOF
|
||||
fi
|
||||
}
|
||||
|
||||
# Build config.yaml.enterprise from the operator's existing config.yaml. We
|
||||
# don't touch the original file. Values go through strenv() so a DSN carrying
|
||||
# quotes, backslashes or $ cannot break out of the expression.
|
||||
# Build config.yaml.enterprise by yq-editing the operator's existing
|
||||
# config.yaml. We don't touch the original file.
|
||||
render_enterprise_config() {
|
||||
{
|
||||
echo "# Generated by migrate-to-enterprise.sh from ${CONFIG_YAML_HOST}."
|
||||
echo "# The enterprise server is started with --config pointing at this file,"
|
||||
echo "# so later edits to ${CONFIG_YAML_HOST} have no effect until copied here."
|
||||
cat "$CONFIG_YAML_HOST"
|
||||
} > "$ENTERPRISE_CONFIG_FILE"
|
||||
local pg_dsn="host=postgres user=netbird password=${POSTGRES_PASSWORD} dbname=netbird port=5432 sslmode=disable"
|
||||
|
||||
if [[ "$MIGRATE_POSTGRES" == "yes" ]]; then
|
||||
# Fresh Postgres: point every store section at it. migrate-store carries the
|
||||
# SQLite contents across.
|
||||
POSTGRES_DSN="$POSTGRES_DSN" yq eval -i '
|
||||
.server.store.engine = "postgres" |
|
||||
.server.store.dsn = strenv(POSTGRES_DSN) |
|
||||
.server.activityStore.engine = "postgres" |
|
||||
.server.activityStore.dsn = strenv(POSTGRES_DSN) |
|
||||
.server.authStore.engine = "postgres" |
|
||||
.server.authStore.dsn = strenv(POSTGRES_DSN)
|
||||
' "$ENTERPRISE_CONFIG_FILE"
|
||||
fi
|
||||
# Otherwise the store config is the operator's and stays untouched.
|
||||
# activityStore and authStore do not inherit from server.store — each falls
|
||||
# back to its own SQLite file under dataDir — so repointing them at Postgres
|
||||
# here would silently strand the existing audit log and the embedded IdP's
|
||||
# users, with no migrate-store run to carry them over.
|
||||
yq eval "
|
||||
.server.store.engine = \"postgres\" |
|
||||
.server.store.dsn = \"$pg_dsn\" |
|
||||
.server.activityStore.engine = \"postgres\" |
|
||||
.server.activityStore.dsn = \"$pg_dsn\" |
|
||||
.server.authStore.engine = \"postgres\" |
|
||||
.server.authStore.dsn = \"$pg_dsn\"
|
||||
" "$CONFIG_YAML_HOST" > "$ENTERPRISE_CONFIG_FILE"
|
||||
|
||||
if [[ "$ENABLE_FLOW" == "yes" ]]; then
|
||||
NETBIRD_DOMAIN="$NETBIRD_DOMAIN" yq eval -i '
|
||||
local flow_addr="${NETBIRD_DOMAIN}"
|
||||
yq eval -i "
|
||||
.server.trafficFlow.enabled = true |
|
||||
.server.trafficFlow.address = strenv(NETBIRD_DOMAIN) |
|
||||
.server.trafficFlow.interval = "60s"
|
||||
' "$ENTERPRISE_CONFIG_FILE"
|
||||
.server.trafficFlow.address = \"$flow_addr\" |
|
||||
.server.trafficFlow.interval = \"60s\"
|
||||
" "$ENTERPRISE_CONFIG_FILE"
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -761,91 +630,6 @@ on_exit() {
|
||||
# Main
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# Already on Postgres: there is nothing to provision and nothing to migrate.
|
||||
# The enterprise image reads the very same store config the community image
|
||||
# did, so step 2 collapses to a no-op and the run is a plain image swap.
|
||||
configure_existing_postgres() {
|
||||
EXISTING_POSTGRES="yes"
|
||||
MIGRATE_POSTGRES="no"
|
||||
|
||||
# DSN first — detect_postgres_service prefers the host it names.
|
||||
POSTGRES_DSN=$(detect_store_dsn)
|
||||
if [[ -z "$POSTGRES_DSN" ]] || [[ "$POSTGRES_DSN" == "null" ]]; then
|
||||
POSTGRES_DSN=$(detect_store_dsn_from_compose)
|
||||
fi
|
||||
if [[ "$POSTGRES_DSN" == "null" ]]; then
|
||||
POSTGRES_DSN=""
|
||||
fi
|
||||
|
||||
POSTGRES_SERVICE=$(detect_postgres_service)
|
||||
if [[ -n "$POSTGRES_SERVICE" ]]; then
|
||||
POSTGRES_DEPENDS_CONDITION=$(detect_postgres_depends_condition)
|
||||
fi
|
||||
|
||||
echo "Step 2: Postgres migration not needed — this deployment already runs on"
|
||||
echo " Postgres. Its store configuration is reused as-is and left"
|
||||
echo " untouched; no database is created and no data is moved."
|
||||
if [[ -n "$POSTGRES_SERVICE" ]]; then
|
||||
echo " Postgres service: $POSTGRES_SERVICE (in $COMPOSE_FILE)"
|
||||
else
|
||||
echo " Postgres service: managed outside $COMPOSE_FILE"
|
||||
fi
|
||||
}
|
||||
|
||||
configure_sqlite_store() {
|
||||
MIGRATE_POSTGRES=$(read_yes_no "Step 2: Migrate storage from SQLite to Postgres? (recommended)" "n")
|
||||
[[ "$MIGRATE_POSTGRES" == "yes" ]] || return 0
|
||||
|
||||
# The override would otherwise merge into a service of the same name and
|
||||
# quietly rewrite its image and credentials.
|
||||
local existing
|
||||
existing=$(yq eval '.services | has("postgres")' "$COMPOSE_FILE")
|
||||
if [[ "$existing" == "true" ]]; then
|
||||
echo "" > /dev/stderr
|
||||
echo "$COMPOSE_FILE already defines a service named 'postgres', but config.yaml" > /dev/stderr
|
||||
echo "still has server.store.engine: sqlite. This script would add its own" > /dev/stderr
|
||||
echo "'postgres' service and Compose would merge the two." > /dev/stderr
|
||||
echo "" > /dev/stderr
|
||||
echo "Point server.store.engine at that Postgres yourself, or rename the service," > /dev/stderr
|
||||
echo "then re-run." > /dev/stderr
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo " ⚠ Data will be migrated from SQLite to Postgres. The SQLite store"
|
||||
echo " will be backed up automatically. To fully revert later, restore"
|
||||
echo " that backup and delete docker-compose.override.yml +"
|
||||
echo " config.yaml.enterprise."
|
||||
local confirm
|
||||
confirm=$(read_yes_no " Continue?" "y")
|
||||
if [[ "$confirm" != "yes" ]]; then
|
||||
MIGRATE_POSTGRES="no"
|
||||
echo " Skipping Postgres migration."
|
||||
return 0
|
||||
fi
|
||||
|
||||
POSTGRES_PASSWORD=$(rand_password)
|
||||
POSTGRES_SERVICE="postgres"
|
||||
POSTGRES_DEPENDS_CONDITION="service_healthy"
|
||||
POSTGRES_DSN="host=postgres user=netbird password=${POSTGRES_PASSWORD} dbname=netbird port=5432 sslmode=disable"
|
||||
}
|
||||
|
||||
# mysql, or something this script has never seen. Swapping the images is still
|
||||
# valid; touching the store is not.
|
||||
configure_unsupported_store() {
|
||||
MIGRATE_POSTGRES="no"
|
||||
echo " ⚠ server.store.engine is '$STORE_ENGINE'. This script only migrates"
|
||||
echo " SQLite to Postgres, and traffic flow requires Postgres, so both are"
|
||||
echo " unavailable here. The store configuration will be left untouched."
|
||||
echo ""
|
||||
local proceed
|
||||
proceed=$(read_yes_no "Step 2 skipped. Continue with the image swap only?" "n")
|
||||
if [[ "$proceed" != "yes" ]]; then
|
||||
echo "Aborted."
|
||||
exit 0
|
||||
fi
|
||||
}
|
||||
|
||||
init_migration() {
|
||||
DOCKER_COMPOSE_COMMAND=$(check_docker_compose)
|
||||
check_yq
|
||||
@@ -895,15 +679,12 @@ init_migration() {
|
||||
exit 1
|
||||
fi
|
||||
|
||||
STORE_ENGINE=$(detect_store_engine)
|
||||
|
||||
echo "Detected existing deployment:"
|
||||
echo " Combined service: $COMBINED_SERVICE"
|
||||
echo " Dashboard: $DASHBOARD_SERVICE"
|
||||
echo " config.yaml: $CONFIG_YAML_HOST"
|
||||
echo " Data volume: $DATA_VOLUME"
|
||||
echo " Network: $COMPOSE_NETWORK"
|
||||
echo " Store engine: $STORE_ENGINE"
|
||||
echo ""
|
||||
|
||||
require_eula_acceptance
|
||||
@@ -922,17 +703,28 @@ init_migration() {
|
||||
echo "Step 1: Image swap (community → Enterprise). License key required."
|
||||
NB_LICENSE_KEY=$(read_secret " License key")
|
||||
|
||||
# Step 2 — what this does depends on what the deployment already stores in.
|
||||
# Step 2 — optional
|
||||
echo ""
|
||||
case "$STORE_ENGINE" in
|
||||
postgres) configure_existing_postgres ;;
|
||||
sqlite) configure_sqlite_store ;;
|
||||
*) configure_unsupported_store ;;
|
||||
esac
|
||||
MIGRATE_POSTGRES=$(read_yes_no "Step 2: Migrate storage from SQLite to Postgres? (recommended)" "n")
|
||||
if [[ "$MIGRATE_POSTGRES" == "yes" ]]; then
|
||||
echo ""
|
||||
echo " ⚠ Data will be migrated from SQLite to Postgres. The SQLite store"
|
||||
echo " will be backed up automatically. To fully revert later, restore"
|
||||
echo " that backup and delete docker-compose.override.yml +"
|
||||
echo " config.yaml.enterprise."
|
||||
local confirm
|
||||
confirm=$(read_yes_no " Continue?" "y")
|
||||
if [[ "$confirm" != "yes" ]]; then
|
||||
MIGRATE_POSTGRES="no"
|
||||
echo " Skipping Postgres migration."
|
||||
else
|
||||
POSTGRES_PASSWORD=$(rand_password)
|
||||
fi
|
||||
fi
|
||||
|
||||
# Step 3 — optional, only if Postgres is on (flow requires Postgres)
|
||||
echo ""
|
||||
if [[ "$MIGRATE_POSTGRES" == "yes" ]] || [[ "$EXISTING_POSTGRES" == "yes" ]]; then
|
||||
if [[ "$MIGRATE_POSTGRES" == "yes" ]]; then
|
||||
ENABLE_FLOW=$(read_yes_no "Step 3: Enable traffic flow? (requires Postgres)" "n")
|
||||
if [[ "$ENABLE_FLOW" == "yes" ]]; then
|
||||
# Auth secret MUST match server.authSecret from config.yaml
|
||||
@@ -956,46 +748,12 @@ init_migration() {
|
||||
echo "Could not read server.store.encryptionKey from $CONFIG_YAML_HOST." > /dev/stderr
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# flow-enricher talks to Postgres directly, so this is the one place an
|
||||
# existing deployment's DSN is actually needed — and the one place a host
|
||||
# that only works from inside the server container shows up.
|
||||
while :; do
|
||||
local dsn_problem=""
|
||||
if [[ -z "$POSTGRES_DSN" ]]; then
|
||||
dsn_problem="No DSN could be read from $CONFIG_YAML_HOST or from the $COMBINED_SERVICE environment."
|
||||
elif ! dsn_host_reachable "$POSTGRES_DSN"; then
|
||||
dsn_problem="Its host '$(dsn_host "$POSTGRES_DSN")' only resolves inside the server container."
|
||||
fi
|
||||
[[ -n "$dsn_problem" ]] || break
|
||||
|
||||
echo ""
|
||||
echo " The flow enricher reaches Postgres from a container of its own."
|
||||
echo " $dsn_problem"
|
||||
echo " Enter a DSN reachable from other containers, or press Ctrl-C to abort."
|
||||
POSTGRES_DSN=$(read_required " Postgres DSN (host=… user=… password=… dbname=… port=5432 sslmode=disable)")
|
||||
done
|
||||
|
||||
# Only where the operator owns Postgres: a DSN entered above may name a
|
||||
# different host. The sqlite path creates its own service, nothing to find.
|
||||
if [[ "$EXISTING_POSTGRES" == "yes" ]]; then
|
||||
POSTGRES_SERVICE=$(detect_postgres_service)
|
||||
if [[ -n "$POSTGRES_SERVICE" ]]; then
|
||||
POSTGRES_DEPENDS_CONDITION=$(detect_postgres_depends_condition)
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
else
|
||||
ENABLE_FLOW="no"
|
||||
echo "Step 3 (traffic flow) skipped — requires Postgres."
|
||||
fi
|
||||
|
||||
# config.yaml.enterprise only exists to hold changes; without any there is
|
||||
# nothing to generate and the server keeps running on its own config.yaml.
|
||||
if [[ "$MIGRATE_POSTGRES" == "yes" ]] || [[ "$ENABLE_FLOW" == "yes" ]]; then
|
||||
ENTERPRISE_CONFIG="yes"
|
||||
fi
|
||||
|
||||
check_data_directory
|
||||
check_stale_postgres_volume
|
||||
}
|
||||
@@ -1013,7 +771,7 @@ apply_changes() {
|
||||
sed -i.bak '/NETBIRD_LICENSE_SERVER_BASE_URL/d' "$OVERRIDE_FILE" && rm -f "$OVERRIDE_FILE.bak"
|
||||
fi
|
||||
|
||||
if [[ "$ENTERPRISE_CONFIG" == "yes" ]]; then
|
||||
if [[ "$MIGRATE_POSTGRES" == "yes" ]]; then
|
||||
echo "Writing $ENTERPRISE_CONFIG_FILE ..."
|
||||
install -m 600 /dev/null "$ENTERPRISE_CONFIG_FILE"
|
||||
render_enterprise_config
|
||||
@@ -1049,9 +807,6 @@ apply_changes() {
|
||||
echo "POSTGRES_PASSWORD=${POSTGRES_PASSWORD}"
|
||||
fi
|
||||
if [[ "$ENABLE_FLOW" == "yes" ]]; then
|
||||
# Own variable name rather than NB_STORE_ENGINE_POSTGRES_DSN so that a
|
||||
# deployment already setting that one keeps its own value.
|
||||
echo "NB_ENTERPRISE_POSTGRES_DSN=$(env_value "$POSTGRES_DSN")"
|
||||
echo "NB_FLOW_AUTH_SECRET=${NB_FLOW_AUTH_SECRET}"
|
||||
echo "NETBIRD_ENCRYPTION_KEY=${NETBIRD_ENCRYPTION_KEY}"
|
||||
fi
|
||||
@@ -1113,19 +868,14 @@ print_summary() {
|
||||
echo " Summary"
|
||||
echo "──────────────────────────────────────────────────────────────────────"
|
||||
echo " Images: swapped to enterprise"
|
||||
if [[ "$MIGRATE_POSTGRES" == "yes" ]]; then
|
||||
echo " Storage: Postgres (data migrated from SQLite)"
|
||||
elif [[ "$EXISTING_POSTGRES" == "yes" ]]; then
|
||||
echo " Storage: Postgres (pre-existing, configuration unchanged)"
|
||||
else
|
||||
echo " Storage: $STORE_ENGINE (unchanged)"
|
||||
fi
|
||||
[[ "$MIGRATE_POSTGRES" == "yes" ]] && echo " Storage: Postgres (data migrated from SQLite)"
|
||||
[[ "$MIGRATE_POSTGRES" != "yes" ]] && echo " Storage: SQLite (unchanged)"
|
||||
[[ "$ENABLE_FLOW" == "yes" ]] && echo " Traffic flow: enabled"
|
||||
[[ "$ENABLE_FLOW" != "yes" ]] && echo " Traffic flow: disabled"
|
||||
echo ""
|
||||
echo " Generated files (next to your docker-compose.yml):"
|
||||
echo " $OVERRIDE_FILE"
|
||||
[[ "$ENTERPRISE_CONFIG" == "yes" ]] && echo " $ENTERPRISE_CONFIG_FILE"
|
||||
[[ "$MIGRATE_POSTGRES" == "yes" ]] && echo " $ENTERPRISE_CONFIG_FILE"
|
||||
echo " .env (license key + secrets, mode 600)"
|
||||
[[ "$ENV_EXISTED" == "yes" ]] && [[ -f "$ENV_BACKUP" ]] && echo " $ENV_BACKUP (.env as it was before this run)"
|
||||
[[ "$MIGRATE_POSTGRES" == "yes" ]] && echo " backups/sqlite-pre-enterprise-*/ (SQLite backup)"
|
||||
@@ -1149,11 +899,7 @@ print_summary() {
|
||||
else
|
||||
echo " $DOCKER_COMPOSE_COMMAND down"
|
||||
fi
|
||||
if [[ "$ENTERPRISE_CONFIG" == "yes" ]]; then
|
||||
echo " rm -f $OVERRIDE_FILE $ENTERPRISE_CONFIG_FILE"
|
||||
else
|
||||
echo " rm -f $OVERRIDE_FILE"
|
||||
fi
|
||||
echo " rm -f $OVERRIDE_FILE $ENTERPRISE_CONFIG_FILE"
|
||||
if [[ "$ENV_EXISTED" == "yes" ]] && [[ -f "$ENV_BACKUP" ]]; then
|
||||
echo " mv $ENV_BACKUP .env # restores .env as it was before this run"
|
||||
elif [[ "$ENV_EXISTED" == "no" ]]; then
|
||||
|
||||
@@ -1024,7 +1024,7 @@ func (c *Controller) OnPeersDeleted(ctx context.Context, accountID string, peerI
|
||||
FirewallRules: []*proto.FirewallRule{},
|
||||
FirewallRulesIsEmpty: true,
|
||||
DNSConfig: &proto.DNSConfig{
|
||||
ForwarderPort: dnsFwdPort, //nolint:staticcheck
|
||||
ForwarderPort: dnsFwdPort,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package network_map
|
||||
|
||||
//go:generate go tool mockgen -package network_map -destination=interface_mock.go -source=./interface.go -build_flags=-mod=mod
|
||||
//go:generate go run go.uber.org/mock/mockgen -package network_map -destination=interface_mock.go -source=./interface.go -build_flags=-mod=mod
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"go.uber.org/mock/gomock"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"go.uber.org/mock/gomock"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"go.uber.org/mock/gomock"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"go.uber.org/mock/gomock"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"go.uber.org/mock/gomock"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"go.uber.org/mock/gomock"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"go.uber.org/mock/gomock"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"go.uber.org/mock/gomock"
|
||||
"github.com/golang/mock/gomock"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package peers
|
||||
|
||||
//go:generate go tool mockgen -package peers -destination=manager_mock.go -source=./manager.go -build_flags=-mod=mod
|
||||
//go:generate go run github.com/golang/mock/mockgen -package peers -destination=manager_mock.go -source=./manager.go -build_flags=-mod=mod
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: ./manager.go
|
||||
//
|
||||
// Generated by this command:
|
||||
//
|
||||
// mockgen -package peers -destination=manager_mock.go -source=./manager.go -build_flags=-mod=mod
|
||||
//
|
||||
|
||||
// Package peers is a generated GoMock package.
|
||||
package peers
|
||||
@@ -14,19 +9,18 @@ import (
|
||||
net "net"
|
||||
reflect "reflect"
|
||||
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
network_map "github.com/netbirdio/netbird/management/internals/controllers/network_map"
|
||||
account "github.com/netbirdio/netbird/management/server/account"
|
||||
integrated_validator "github.com/netbirdio/netbird/management/server/integrations/integrated_validator"
|
||||
peer "github.com/netbirdio/netbird/management/server/peer"
|
||||
types "github.com/netbirdio/netbird/management/server/types"
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
// MockManager is a mock of Manager interface.
|
||||
type MockManager struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockManagerMockRecorder
|
||||
isgomock struct{}
|
||||
}
|
||||
|
||||
// MockManagerMockRecorder is the mock recorder for MockManager.
|
||||
@@ -55,7 +49,7 @@ func (m *MockManager) CreateProxyPeer(ctx context.Context, accountID, peerKey, c
|
||||
}
|
||||
|
||||
// CreateProxyPeer indicates an expected call of CreateProxyPeer.
|
||||
func (mr *MockManagerMockRecorder) CreateProxyPeer(ctx, accountID, peerKey, cluster any) *gomock.Call {
|
||||
func (mr *MockManagerMockRecorder) CreateProxyPeer(ctx, accountID, peerKey, cluster interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateProxyPeer", reflect.TypeOf((*MockManager)(nil).CreateProxyPeer), ctx, accountID, peerKey, cluster)
|
||||
}
|
||||
@@ -69,7 +63,7 @@ func (m *MockManager) DeletePeers(ctx context.Context, accountID string, peerIDs
|
||||
}
|
||||
|
||||
// DeletePeers indicates an expected call of DeletePeers.
|
||||
func (mr *MockManagerMockRecorder) DeletePeers(ctx, accountID, peerIDs, userID, checkConnected any) *gomock.Call {
|
||||
func (mr *MockManagerMockRecorder) DeletePeers(ctx, accountID, peerIDs, userID, checkConnected interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeletePeers", reflect.TypeOf((*MockManager)(nil).DeletePeers), ctx, accountID, peerIDs, userID, checkConnected)
|
||||
}
|
||||
@@ -84,7 +78,7 @@ func (m *MockManager) GetAllPeers(ctx context.Context, accountID, userID string)
|
||||
}
|
||||
|
||||
// GetAllPeers indicates an expected call of GetAllPeers.
|
||||
func (mr *MockManagerMockRecorder) GetAllPeers(ctx, accountID, userID any) *gomock.Call {
|
||||
func (mr *MockManagerMockRecorder) GetAllPeers(ctx, accountID, userID interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAllPeers", reflect.TypeOf((*MockManager)(nil).GetAllPeers), ctx, accountID, userID)
|
||||
}
|
||||
@@ -99,7 +93,7 @@ func (m *MockManager) GetPeer(ctx context.Context, accountID, userID, peerID str
|
||||
}
|
||||
|
||||
// GetPeer indicates an expected call of GetPeer.
|
||||
func (mr *MockManagerMockRecorder) GetPeer(ctx, accountID, userID, peerID any) *gomock.Call {
|
||||
func (mr *MockManagerMockRecorder) GetPeer(ctx, accountID, userID, peerID interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPeer", reflect.TypeOf((*MockManager)(nil).GetPeer), ctx, accountID, userID, peerID)
|
||||
}
|
||||
@@ -114,7 +108,7 @@ func (m *MockManager) GetPeerAccountID(ctx context.Context, peerID string) (stri
|
||||
}
|
||||
|
||||
// GetPeerAccountID indicates an expected call of GetPeerAccountID.
|
||||
func (mr *MockManagerMockRecorder) GetPeerAccountID(ctx, peerID any) *gomock.Call {
|
||||
func (mr *MockManagerMockRecorder) GetPeerAccountID(ctx, peerID interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPeerAccountID", reflect.TypeOf((*MockManager)(nil).GetPeerAccountID), ctx, peerID)
|
||||
}
|
||||
@@ -129,7 +123,7 @@ func (m *MockManager) GetPeerByTunnelIP(ctx context.Context, accountID string, i
|
||||
}
|
||||
|
||||
// GetPeerByTunnelIP indicates an expected call of GetPeerByTunnelIP.
|
||||
func (mr *MockManagerMockRecorder) GetPeerByTunnelIP(ctx, accountID, ip any) *gomock.Call {
|
||||
func (mr *MockManagerMockRecorder) GetPeerByTunnelIP(ctx, accountID, ip interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPeerByTunnelIP", reflect.TypeOf((*MockManager)(nil).GetPeerByTunnelIP), ctx, accountID, ip)
|
||||
}
|
||||
@@ -144,7 +138,7 @@ func (m *MockManager) GetPeerID(ctx context.Context, peerKey string) (string, er
|
||||
}
|
||||
|
||||
// GetPeerID indicates an expected call of GetPeerID.
|
||||
func (mr *MockManagerMockRecorder) GetPeerID(ctx, peerKey any) *gomock.Call {
|
||||
func (mr *MockManagerMockRecorder) GetPeerID(ctx, peerKey interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPeerID", reflect.TypeOf((*MockManager)(nil).GetPeerID), ctx, peerKey)
|
||||
}
|
||||
@@ -160,7 +154,7 @@ func (m *MockManager) GetPeerWithGroups(ctx context.Context, accountID, peerID s
|
||||
}
|
||||
|
||||
// GetPeerWithGroups indicates an expected call of GetPeerWithGroups.
|
||||
func (mr *MockManagerMockRecorder) GetPeerWithGroups(ctx, accountID, peerID any) *gomock.Call {
|
||||
func (mr *MockManagerMockRecorder) GetPeerWithGroups(ctx, accountID, peerID interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPeerWithGroups", reflect.TypeOf((*MockManager)(nil).GetPeerWithGroups), ctx, accountID, peerID)
|
||||
}
|
||||
@@ -175,7 +169,7 @@ func (m *MockManager) GetPeersByGroupIDs(ctx context.Context, accountID string,
|
||||
}
|
||||
|
||||
// GetPeersByGroupIDs indicates an expected call of GetPeersByGroupIDs.
|
||||
func (mr *MockManagerMockRecorder) GetPeersByGroupIDs(ctx, accountID, groupsIDs any) *gomock.Call {
|
||||
func (mr *MockManagerMockRecorder) GetPeersByGroupIDs(ctx, accountID, groupsIDs interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPeersByGroupIDs", reflect.TypeOf((*MockManager)(nil).GetPeersByGroupIDs), ctx, accountID, groupsIDs)
|
||||
}
|
||||
@@ -187,7 +181,7 @@ func (m *MockManager) SetAccountManager(accountManager account.Manager) {
|
||||
}
|
||||
|
||||
// SetAccountManager indicates an expected call of SetAccountManager.
|
||||
func (mr *MockManagerMockRecorder) SetAccountManager(accountManager any) *gomock.Call {
|
||||
func (mr *MockManagerMockRecorder) SetAccountManager(accountManager interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetAccountManager", reflect.TypeOf((*MockManager)(nil).SetAccountManager), accountManager)
|
||||
}
|
||||
@@ -199,7 +193,7 @@ func (m *MockManager) SetIntegratedPeerValidator(integratedPeerValidator integra
|
||||
}
|
||||
|
||||
// SetIntegratedPeerValidator indicates an expected call of SetIntegratedPeerValidator.
|
||||
func (mr *MockManagerMockRecorder) SetIntegratedPeerValidator(integratedPeerValidator any) *gomock.Call {
|
||||
func (mr *MockManagerMockRecorder) SetIntegratedPeerValidator(integratedPeerValidator interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetIntegratedPeerValidator", reflect.TypeOf((*MockManager)(nil).SetIntegratedPeerValidator), integratedPeerValidator)
|
||||
}
|
||||
@@ -211,7 +205,7 @@ func (m *MockManager) SetNetworkMapController(networkMapController network_map.C
|
||||
}
|
||||
|
||||
// SetNetworkMapController indicates an expected call of SetNetworkMapController.
|
||||
func (mr *MockManagerMockRecorder) SetNetworkMapController(networkMapController any) *gomock.Call {
|
||||
func (mr *MockManagerMockRecorder) SetNetworkMapController(networkMapController interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetNetworkMapController", reflect.TypeOf((*MockManager)(nil).SetNetworkMapController), networkMapController)
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"go.uber.org/mock/gomock"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package proxy
|
||||
|
||||
//go:generate go tool mockgen -package proxy -destination=manager_mock.go -source=./manager.go -build_flags=-mod=mod
|
||||
//go:generate go run github.com/golang/mock/mockgen -package proxy -destination=manager_mock.go -source=./manager.go -build_flags=-mod=mod
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: ./manager.go
|
||||
//
|
||||
// Generated by this command:
|
||||
//
|
||||
// mockgen -package proxy -destination=manager_mock.go -source=./manager.go -build_flags=-mod=mod
|
||||
//
|
||||
|
||||
// Package proxy is a generated GoMock package.
|
||||
package proxy
|
||||
@@ -14,15 +9,14 @@ import (
|
||||
reflect "reflect"
|
||||
time "time"
|
||||
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
proto "github.com/netbirdio/netbird/shared/management/proto"
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
// MockManager is a mock of Manager interface.
|
||||
type MockManager struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockManagerMockRecorder
|
||||
isgomock struct{}
|
||||
}
|
||||
|
||||
// MockManagerMockRecorder is the mock recorder for MockManager.
|
||||
@@ -51,11 +45,25 @@ func (m *MockManager) CleanupStale(ctx context.Context, inactivityDuration time.
|
||||
}
|
||||
|
||||
// CleanupStale indicates an expected call of CleanupStale.
|
||||
func (mr *MockManagerMockRecorder) CleanupStale(ctx, inactivityDuration any) *gomock.Call {
|
||||
func (mr *MockManagerMockRecorder) CleanupStale(ctx, inactivityDuration interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CleanupStale", reflect.TypeOf((*MockManager)(nil).CleanupStale), ctx, inactivityDuration)
|
||||
}
|
||||
|
||||
// ClusterSupportsCustomPorts mocks base method.
|
||||
func (m *MockManager) ClusterSupportsCustomPorts(ctx context.Context, clusterAddr string) *bool {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "ClusterSupportsCustomPorts", ctx, clusterAddr)
|
||||
ret0, _ := ret[0].(*bool)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// ClusterSupportsCustomPorts indicates an expected call of ClusterSupportsCustomPorts.
|
||||
func (mr *MockManagerMockRecorder) ClusterSupportsCustomPorts(ctx, clusterAddr interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClusterSupportsCustomPorts", reflect.TypeOf((*MockManager)(nil).ClusterSupportsCustomPorts), ctx, clusterAddr)
|
||||
}
|
||||
|
||||
// ClusterRequireSubdomain mocks base method.
|
||||
func (m *MockManager) ClusterRequireSubdomain(ctx context.Context, clusterAddr string) *bool {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -65,7 +73,7 @@ func (m *MockManager) ClusterRequireSubdomain(ctx context.Context, clusterAddr s
|
||||
}
|
||||
|
||||
// ClusterRequireSubdomain indicates an expected call of ClusterRequireSubdomain.
|
||||
func (mr *MockManagerMockRecorder) ClusterRequireSubdomain(ctx, clusterAddr any) *gomock.Call {
|
||||
func (mr *MockManagerMockRecorder) ClusterRequireSubdomain(ctx, clusterAddr interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClusterRequireSubdomain", reflect.TypeOf((*MockManager)(nil).ClusterRequireSubdomain), ctx, clusterAddr)
|
||||
}
|
||||
@@ -79,25 +87,11 @@ func (m *MockManager) ClusterSupportsCrowdSec(ctx context.Context, clusterAddr s
|
||||
}
|
||||
|
||||
// ClusterSupportsCrowdSec indicates an expected call of ClusterSupportsCrowdSec.
|
||||
func (mr *MockManagerMockRecorder) ClusterSupportsCrowdSec(ctx, clusterAddr any) *gomock.Call {
|
||||
func (mr *MockManagerMockRecorder) ClusterSupportsCrowdSec(ctx, clusterAddr interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClusterSupportsCrowdSec", reflect.TypeOf((*MockManager)(nil).ClusterSupportsCrowdSec), ctx, clusterAddr)
|
||||
}
|
||||
|
||||
// ClusterSupportsCustomPorts mocks base method.
|
||||
func (m *MockManager) ClusterSupportsCustomPorts(ctx context.Context, clusterAddr string) *bool {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "ClusterSupportsCustomPorts", ctx, clusterAddr)
|
||||
ret0, _ := ret[0].(*bool)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// ClusterSupportsCustomPorts indicates an expected call of ClusterSupportsCustomPorts.
|
||||
func (mr *MockManagerMockRecorder) ClusterSupportsCustomPorts(ctx, clusterAddr any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClusterSupportsCustomPorts", reflect.TypeOf((*MockManager)(nil).ClusterSupportsCustomPorts), ctx, clusterAddr)
|
||||
}
|
||||
|
||||
// ClusterSupportsPrivate mocks base method.
|
||||
func (m *MockManager) ClusterSupportsPrivate(ctx context.Context, clusterAddr string) *bool {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -107,7 +101,7 @@ func (m *MockManager) ClusterSupportsPrivate(ctx context.Context, clusterAddr st
|
||||
}
|
||||
|
||||
// ClusterSupportsPrivate indicates an expected call of ClusterSupportsPrivate.
|
||||
func (mr *MockManagerMockRecorder) ClusterSupportsPrivate(ctx, clusterAddr any) *gomock.Call {
|
||||
func (mr *MockManagerMockRecorder) ClusterSupportsPrivate(ctx, clusterAddr interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClusterSupportsPrivate", reflect.TypeOf((*MockManager)(nil).ClusterSupportsPrivate), ctx, clusterAddr)
|
||||
}
|
||||
@@ -122,40 +116,11 @@ func (m *MockManager) Connect(ctx context.Context, proxyID, sessionID, clusterAd
|
||||
}
|
||||
|
||||
// Connect indicates an expected call of Connect.
|
||||
func (mr *MockManagerMockRecorder) Connect(ctx, proxyID, sessionID, clusterAddress, ipAddress, accountID, capabilities any) *gomock.Call {
|
||||
func (mr *MockManagerMockRecorder) Connect(ctx, proxyID, sessionID, clusterAddress, ipAddress, accountID, capabilities interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Connect", reflect.TypeOf((*MockManager)(nil).Connect), ctx, proxyID, sessionID, clusterAddress, ipAddress, accountID, capabilities)
|
||||
}
|
||||
|
||||
// CountAccountProxies mocks base method.
|
||||
func (m *MockManager) CountAccountProxies(ctx context.Context, accountID string) (int64, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "CountAccountProxies", ctx, accountID)
|
||||
ret0, _ := ret[0].(int64)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// CountAccountProxies indicates an expected call of CountAccountProxies.
|
||||
func (mr *MockManagerMockRecorder) CountAccountProxies(ctx, accountID any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CountAccountProxies", reflect.TypeOf((*MockManager)(nil).CountAccountProxies), ctx, accountID)
|
||||
}
|
||||
|
||||
// DeleteAccountCluster mocks base method.
|
||||
func (m *MockManager) DeleteAccountCluster(ctx context.Context, clusterAddress, accountID string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "DeleteAccountCluster", ctx, clusterAddress, accountID)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// DeleteAccountCluster indicates an expected call of DeleteAccountCluster.
|
||||
func (mr *MockManagerMockRecorder) DeleteAccountCluster(ctx, clusterAddress, accountID any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteAccountCluster", reflect.TypeOf((*MockManager)(nil).DeleteAccountCluster), ctx, clusterAddress, accountID)
|
||||
}
|
||||
|
||||
// Disconnect mocks base method.
|
||||
func (m *MockManager) Disconnect(ctx context.Context, proxyID, sessionID string) error {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -165,26 +130,11 @@ func (m *MockManager) Disconnect(ctx context.Context, proxyID, sessionID string)
|
||||
}
|
||||
|
||||
// Disconnect indicates an expected call of Disconnect.
|
||||
func (mr *MockManagerMockRecorder) Disconnect(ctx, proxyID, sessionID any) *gomock.Call {
|
||||
func (mr *MockManagerMockRecorder) Disconnect(ctx, proxyID, sessionID interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Disconnect", reflect.TypeOf((*MockManager)(nil).Disconnect), ctx, proxyID, sessionID)
|
||||
}
|
||||
|
||||
// GetAccountProxy mocks base method.
|
||||
func (m *MockManager) GetAccountProxy(ctx context.Context, accountID string) (*Proxy, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetAccountProxy", ctx, accountID)
|
||||
ret0, _ := ret[0].(*Proxy)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetAccountProxy indicates an expected call of GetAccountProxy.
|
||||
func (mr *MockManagerMockRecorder) GetAccountProxy(ctx, accountID any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAccountProxy", reflect.TypeOf((*MockManager)(nil).GetAccountProxy), ctx, accountID)
|
||||
}
|
||||
|
||||
// GetActiveClusterAddresses mocks base method.
|
||||
func (m *MockManager) GetActiveClusterAddresses(ctx context.Context) ([]string, error) {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -195,12 +145,11 @@ func (m *MockManager) GetActiveClusterAddresses(ctx context.Context) ([]string,
|
||||
}
|
||||
|
||||
// GetActiveClusterAddresses indicates an expected call of GetActiveClusterAddresses.
|
||||
func (mr *MockManagerMockRecorder) GetActiveClusterAddresses(ctx any) *gomock.Call {
|
||||
func (mr *MockManagerMockRecorder) GetActiveClusterAddresses(ctx interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetActiveClusterAddresses", reflect.TypeOf((*MockManager)(nil).GetActiveClusterAddresses), ctx)
|
||||
}
|
||||
|
||||
// GetActiveClusterAddressesForAccount mocks base method.
|
||||
func (m *MockManager) GetActiveClusterAddressesForAccount(ctx context.Context, accountID string) ([]string, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetActiveClusterAddressesForAccount", ctx, accountID)
|
||||
@@ -209,8 +158,7 @@ func (m *MockManager) GetActiveClusterAddressesForAccount(ctx context.Context, a
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetActiveClusterAddressesForAccount indicates an expected call of GetActiveClusterAddressesForAccount.
|
||||
func (mr *MockManagerMockRecorder) GetActiveClusterAddressesForAccount(ctx, accountID any) *gomock.Call {
|
||||
func (mr *MockManagerMockRecorder) GetActiveClusterAddressesForAccount(ctx, accountID interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetActiveClusterAddressesForAccount", reflect.TypeOf((*MockManager)(nil).GetActiveClusterAddressesForAccount), ctx, accountID)
|
||||
}
|
||||
@@ -224,11 +172,41 @@ func (m *MockManager) Heartbeat(ctx context.Context, p *Proxy) error {
|
||||
}
|
||||
|
||||
// Heartbeat indicates an expected call of Heartbeat.
|
||||
func (mr *MockManagerMockRecorder) Heartbeat(ctx, p any) *gomock.Call {
|
||||
func (mr *MockManagerMockRecorder) Heartbeat(ctx, p interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Heartbeat", reflect.TypeOf((*MockManager)(nil).Heartbeat), ctx, p)
|
||||
}
|
||||
|
||||
// GetAccountProxy mocks base method.
|
||||
func (m *MockManager) GetAccountProxy(ctx context.Context, accountID string) (*Proxy, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetAccountProxy", ctx, accountID)
|
||||
ret0, _ := ret[0].(*Proxy)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetAccountProxy indicates an expected call of GetAccountProxy.
|
||||
func (mr *MockManagerMockRecorder) GetAccountProxy(ctx, accountID interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAccountProxy", reflect.TypeOf((*MockManager)(nil).GetAccountProxy), ctx, accountID)
|
||||
}
|
||||
|
||||
// CountAccountProxies mocks base method.
|
||||
func (m *MockManager) CountAccountProxies(ctx context.Context, accountID string) (int64, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "CountAccountProxies", ctx, accountID)
|
||||
ret0, _ := ret[0].(int64)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// CountAccountProxies indicates an expected call of CountAccountProxies.
|
||||
func (mr *MockManagerMockRecorder) CountAccountProxies(ctx, accountID interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CountAccountProxies", reflect.TypeOf((*MockManager)(nil).CountAccountProxies), ctx, accountID)
|
||||
}
|
||||
|
||||
// IsClusterAddressAvailable mocks base method.
|
||||
func (m *MockManager) IsClusterAddressAvailable(ctx context.Context, clusterAddress, accountID string) (bool, error) {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -239,16 +217,29 @@ func (m *MockManager) IsClusterAddressAvailable(ctx context.Context, clusterAddr
|
||||
}
|
||||
|
||||
// IsClusterAddressAvailable indicates an expected call of IsClusterAddressAvailable.
|
||||
func (mr *MockManagerMockRecorder) IsClusterAddressAvailable(ctx, clusterAddress, accountID any) *gomock.Call {
|
||||
func (mr *MockManagerMockRecorder) IsClusterAddressAvailable(ctx, clusterAddress, accountID interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsClusterAddressAvailable", reflect.TypeOf((*MockManager)(nil).IsClusterAddressAvailable), ctx, clusterAddress, accountID)
|
||||
}
|
||||
|
||||
// DeleteAccountCluster mocks base method.
|
||||
func (m *MockManager) DeleteAccountCluster(ctx context.Context, clusterAddress, accountID string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "DeleteAccountCluster", ctx, clusterAddress, accountID)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// DeleteAccountCluster indicates an expected call of DeleteAccountCluster.
|
||||
func (mr *MockManagerMockRecorder) DeleteAccountCluster(ctx, clusterAddress, accountID interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteAccountCluster", reflect.TypeOf((*MockManager)(nil).DeleteAccountCluster), ctx, clusterAddress, accountID)
|
||||
}
|
||||
|
||||
// MockController is a mock of Controller interface.
|
||||
type MockController struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockControllerMockRecorder
|
||||
isgomock struct{}
|
||||
}
|
||||
|
||||
// MockControllerMockRecorder is the mock recorder for MockController.
|
||||
@@ -291,7 +282,7 @@ func (m *MockController) GetProxiesForCluster(clusterAddr string) []string {
|
||||
}
|
||||
|
||||
// GetProxiesForCluster indicates an expected call of GetProxiesForCluster.
|
||||
func (mr *MockControllerMockRecorder) GetProxiesForCluster(clusterAddr any) *gomock.Call {
|
||||
func (mr *MockControllerMockRecorder) GetProxiesForCluster(clusterAddr interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetProxiesForCluster", reflect.TypeOf((*MockController)(nil).GetProxiesForCluster), clusterAddr)
|
||||
}
|
||||
@@ -305,7 +296,7 @@ func (m *MockController) RegisterProxyToCluster(ctx context.Context, clusterAddr
|
||||
}
|
||||
|
||||
// RegisterProxyToCluster indicates an expected call of RegisterProxyToCluster.
|
||||
func (mr *MockControllerMockRecorder) RegisterProxyToCluster(ctx, clusterAddr, proxyID any) *gomock.Call {
|
||||
func (mr *MockControllerMockRecorder) RegisterProxyToCluster(ctx, clusterAddr, proxyID interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RegisterProxyToCluster", reflect.TypeOf((*MockController)(nil).RegisterProxyToCluster), ctx, clusterAddr, proxyID)
|
||||
}
|
||||
@@ -317,7 +308,7 @@ func (m *MockController) SendServiceUpdateToCluster(ctx context.Context, account
|
||||
}
|
||||
|
||||
// SendServiceUpdateToCluster indicates an expected call of SendServiceUpdateToCluster.
|
||||
func (mr *MockControllerMockRecorder) SendServiceUpdateToCluster(ctx, accountID, update, clusterAddr any) *gomock.Call {
|
||||
func (mr *MockControllerMockRecorder) SendServiceUpdateToCluster(ctx, accountID, update, clusterAddr interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendServiceUpdateToCluster", reflect.TypeOf((*MockController)(nil).SendServiceUpdateToCluster), ctx, accountID, update, clusterAddr)
|
||||
}
|
||||
@@ -331,7 +322,7 @@ func (m *MockController) UnregisterProxyFromCluster(ctx context.Context, cluster
|
||||
}
|
||||
|
||||
// UnregisterProxyFromCluster indicates an expected call of UnregisterProxyFromCluster.
|
||||
func (mr *MockControllerMockRecorder) UnregisterProxyFromCluster(ctx, clusterAddr, proxyID any) *gomock.Call {
|
||||
func (mr *MockControllerMockRecorder) UnregisterProxyFromCluster(ctx, clusterAddr, proxyID interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UnregisterProxyFromCluster", reflect.TypeOf((*MockController)(nil).UnregisterProxyFromCluster), ctx, clusterAddr, proxyID)
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"go.uber.org/mock/gomock"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package service
|
||||
|
||||
//go:generate go tool mockgen -package service -destination=interface_mock.go -source=./interface.go -build_flags=-mod=mod
|
||||
//go:generate go run github.com/golang/mock/mockgen -package service -destination=interface_mock.go -source=./interface.go -build_flags=-mod=mod
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: ./interface.go
|
||||
//
|
||||
// Generated by this command:
|
||||
//
|
||||
// mockgen -package service -destination=interface_mock.go -source=./interface.go -build_flags=-mod=mod
|
||||
//
|
||||
|
||||
// Package service is a generated GoMock package.
|
||||
package service
|
||||
@@ -13,15 +8,14 @@ import (
|
||||
context "context"
|
||||
reflect "reflect"
|
||||
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
proxy "github.com/netbirdio/netbird/management/internals/modules/reverseproxy/proxy"
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
// MockManager is a mock of Manager interface.
|
||||
type MockManager struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockManagerMockRecorder
|
||||
isgomock struct{}
|
||||
}
|
||||
|
||||
// MockManagerMockRecorder is the mock recorder for MockManager.
|
||||
@@ -51,7 +45,7 @@ func (m *MockManager) CreateService(ctx context.Context, accountID, userID strin
|
||||
}
|
||||
|
||||
// CreateService indicates an expected call of CreateService.
|
||||
func (mr *MockManagerMockRecorder) CreateService(ctx, accountID, userID, service any) *gomock.Call {
|
||||
func (mr *MockManagerMockRecorder) CreateService(ctx, accountID, userID, service interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateService", reflect.TypeOf((*MockManager)(nil).CreateService), ctx, accountID, userID, service)
|
||||
}
|
||||
@@ -66,7 +60,7 @@ func (m *MockManager) CreateServiceFromPeer(ctx context.Context, accountID, peer
|
||||
}
|
||||
|
||||
// CreateServiceFromPeer indicates an expected call of CreateServiceFromPeer.
|
||||
func (mr *MockManagerMockRecorder) CreateServiceFromPeer(ctx, accountID, peerID, req any) *gomock.Call {
|
||||
func (mr *MockManagerMockRecorder) CreateServiceFromPeer(ctx, accountID, peerID, req interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateServiceFromPeer", reflect.TypeOf((*MockManager)(nil).CreateServiceFromPeer), ctx, accountID, peerID, req)
|
||||
}
|
||||
@@ -80,7 +74,7 @@ func (m *MockManager) DeleteAccountCluster(ctx context.Context, accountID, userI
|
||||
}
|
||||
|
||||
// DeleteAccountCluster indicates an expected call of DeleteAccountCluster.
|
||||
func (mr *MockManagerMockRecorder) DeleteAccountCluster(ctx, accountID, userID, clusterAddress any) *gomock.Call {
|
||||
func (mr *MockManagerMockRecorder) DeleteAccountCluster(ctx, accountID, userID, clusterAddress interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteAccountCluster", reflect.TypeOf((*MockManager)(nil).DeleteAccountCluster), ctx, accountID, userID, clusterAddress)
|
||||
}
|
||||
@@ -94,7 +88,7 @@ func (m *MockManager) DeleteAllServices(ctx context.Context, accountID, userID s
|
||||
}
|
||||
|
||||
// DeleteAllServices indicates an expected call of DeleteAllServices.
|
||||
func (mr *MockManagerMockRecorder) DeleteAllServices(ctx, accountID, userID any) *gomock.Call {
|
||||
func (mr *MockManagerMockRecorder) DeleteAllServices(ctx, accountID, userID interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteAllServices", reflect.TypeOf((*MockManager)(nil).DeleteAllServices), ctx, accountID, userID)
|
||||
}
|
||||
@@ -108,7 +102,7 @@ func (m *MockManager) DeleteService(ctx context.Context, accountID, userID, serv
|
||||
}
|
||||
|
||||
// DeleteService indicates an expected call of DeleteService.
|
||||
func (mr *MockManagerMockRecorder) DeleteService(ctx, accountID, userID, serviceID any) *gomock.Call {
|
||||
func (mr *MockManagerMockRecorder) DeleteService(ctx, accountID, userID, serviceID interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteService", reflect.TypeOf((*MockManager)(nil).DeleteService), ctx, accountID, userID, serviceID)
|
||||
}
|
||||
@@ -123,7 +117,7 @@ func (m *MockManager) GetAccountServices(ctx context.Context, accountID string)
|
||||
}
|
||||
|
||||
// GetAccountServices indicates an expected call of GetAccountServices.
|
||||
func (mr *MockManagerMockRecorder) GetAccountServices(ctx, accountID any) *gomock.Call {
|
||||
func (mr *MockManagerMockRecorder) GetAccountServices(ctx, accountID interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAccountServices", reflect.TypeOf((*MockManager)(nil).GetAccountServices), ctx, accountID)
|
||||
}
|
||||
@@ -138,7 +132,7 @@ func (m *MockManager) GetAllServices(ctx context.Context, accountID, userID stri
|
||||
}
|
||||
|
||||
// GetAllServices indicates an expected call of GetAllServices.
|
||||
func (mr *MockManagerMockRecorder) GetAllServices(ctx, accountID, userID any) *gomock.Call {
|
||||
func (mr *MockManagerMockRecorder) GetAllServices(ctx, accountID, userID interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAllServices", reflect.TypeOf((*MockManager)(nil).GetAllServices), ctx, accountID, userID)
|
||||
}
|
||||
@@ -153,7 +147,7 @@ func (m *MockManager) GetClusters(ctx context.Context, accountID, userID string)
|
||||
}
|
||||
|
||||
// GetClusters indicates an expected call of GetClusters.
|
||||
func (mr *MockManagerMockRecorder) GetClusters(ctx, accountID, userID any) *gomock.Call {
|
||||
func (mr *MockManagerMockRecorder) GetClusters(ctx, accountID, userID interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetClusters", reflect.TypeOf((*MockManager)(nil).GetClusters), ctx, accountID, userID)
|
||||
}
|
||||
@@ -168,7 +162,7 @@ func (m *MockManager) GetGlobalServices(ctx context.Context) ([]*Service, error)
|
||||
}
|
||||
|
||||
// GetGlobalServices indicates an expected call of GetGlobalServices.
|
||||
func (mr *MockManagerMockRecorder) GetGlobalServices(ctx any) *gomock.Call {
|
||||
func (mr *MockManagerMockRecorder) GetGlobalServices(ctx interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetGlobalServices", reflect.TypeOf((*MockManager)(nil).GetGlobalServices), ctx)
|
||||
}
|
||||
@@ -183,7 +177,7 @@ func (m *MockManager) GetService(ctx context.Context, accountID, userID, service
|
||||
}
|
||||
|
||||
// GetService indicates an expected call of GetService.
|
||||
func (mr *MockManagerMockRecorder) GetService(ctx, accountID, userID, serviceID any) *gomock.Call {
|
||||
func (mr *MockManagerMockRecorder) GetService(ctx, accountID, userID, serviceID interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetService", reflect.TypeOf((*MockManager)(nil).GetService), ctx, accountID, userID, serviceID)
|
||||
}
|
||||
@@ -198,7 +192,7 @@ func (m *MockManager) GetServiceByDomain(ctx context.Context, domain string) (*S
|
||||
}
|
||||
|
||||
// GetServiceByDomain indicates an expected call of GetServiceByDomain.
|
||||
func (mr *MockManagerMockRecorder) GetServiceByDomain(ctx, domain any) *gomock.Call {
|
||||
func (mr *MockManagerMockRecorder) GetServiceByDomain(ctx, domain interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetServiceByDomain", reflect.TypeOf((*MockManager)(nil).GetServiceByDomain), ctx, domain)
|
||||
}
|
||||
@@ -213,7 +207,7 @@ func (m *MockManager) GetServiceByID(ctx context.Context, accountID, serviceID s
|
||||
}
|
||||
|
||||
// GetServiceByID indicates an expected call of GetServiceByID.
|
||||
func (mr *MockManagerMockRecorder) GetServiceByID(ctx, accountID, serviceID any) *gomock.Call {
|
||||
func (mr *MockManagerMockRecorder) GetServiceByID(ctx, accountID, serviceID interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetServiceByID", reflect.TypeOf((*MockManager)(nil).GetServiceByID), ctx, accountID, serviceID)
|
||||
}
|
||||
@@ -228,7 +222,7 @@ func (m *MockManager) GetServiceIDByTargetID(ctx context.Context, accountID, res
|
||||
}
|
||||
|
||||
// GetServiceIDByTargetID indicates an expected call of GetServiceIDByTargetID.
|
||||
func (mr *MockManagerMockRecorder) GetServiceIDByTargetID(ctx, accountID, resourceID any) *gomock.Call {
|
||||
func (mr *MockManagerMockRecorder) GetServiceIDByTargetID(ctx, accountID, resourceID interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetServiceIDByTargetID", reflect.TypeOf((*MockManager)(nil).GetServiceIDByTargetID), ctx, accountID, resourceID)
|
||||
}
|
||||
@@ -242,7 +236,7 @@ func (m *MockManager) ReloadAllServicesForAccount(ctx context.Context, accountID
|
||||
}
|
||||
|
||||
// ReloadAllServicesForAccount indicates an expected call of ReloadAllServicesForAccount.
|
||||
func (mr *MockManagerMockRecorder) ReloadAllServicesForAccount(ctx, accountID any) *gomock.Call {
|
||||
func (mr *MockManagerMockRecorder) ReloadAllServicesForAccount(ctx, accountID interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReloadAllServicesForAccount", reflect.TypeOf((*MockManager)(nil).ReloadAllServicesForAccount), ctx, accountID)
|
||||
}
|
||||
@@ -256,7 +250,7 @@ func (m *MockManager) ReloadService(ctx context.Context, accountID, serviceID st
|
||||
}
|
||||
|
||||
// ReloadService indicates an expected call of ReloadService.
|
||||
func (mr *MockManagerMockRecorder) ReloadService(ctx, accountID, serviceID any) *gomock.Call {
|
||||
func (mr *MockManagerMockRecorder) ReloadService(ctx, accountID, serviceID interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReloadService", reflect.TypeOf((*MockManager)(nil).ReloadService), ctx, accountID, serviceID)
|
||||
}
|
||||
@@ -270,7 +264,7 @@ func (m *MockManager) RenewServiceFromPeer(ctx context.Context, accountID, peerI
|
||||
}
|
||||
|
||||
// RenewServiceFromPeer indicates an expected call of RenewServiceFromPeer.
|
||||
func (mr *MockManagerMockRecorder) RenewServiceFromPeer(ctx, accountID, peerID, serviceID any) *gomock.Call {
|
||||
func (mr *MockManagerMockRecorder) RenewServiceFromPeer(ctx, accountID, peerID, serviceID interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RenewServiceFromPeer", reflect.TypeOf((*MockManager)(nil).RenewServiceFromPeer), ctx, accountID, peerID, serviceID)
|
||||
}
|
||||
@@ -284,7 +278,7 @@ func (m *MockManager) SetCertificateIssuedAt(ctx context.Context, accountID, ser
|
||||
}
|
||||
|
||||
// SetCertificateIssuedAt indicates an expected call of SetCertificateIssuedAt.
|
||||
func (mr *MockManagerMockRecorder) SetCertificateIssuedAt(ctx, accountID, serviceID any) *gomock.Call {
|
||||
func (mr *MockManagerMockRecorder) SetCertificateIssuedAt(ctx, accountID, serviceID interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetCertificateIssuedAt", reflect.TypeOf((*MockManager)(nil).SetCertificateIssuedAt), ctx, accountID, serviceID)
|
||||
}
|
||||
@@ -298,7 +292,7 @@ func (m *MockManager) SetStatus(ctx context.Context, accountID, serviceID string
|
||||
}
|
||||
|
||||
// SetStatus indicates an expected call of SetStatus.
|
||||
func (mr *MockManagerMockRecorder) SetStatus(ctx, accountID, serviceID, status any) *gomock.Call {
|
||||
func (mr *MockManagerMockRecorder) SetStatus(ctx, accountID, serviceID, status interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetStatus", reflect.TypeOf((*MockManager)(nil).SetStatus), ctx, accountID, serviceID, status)
|
||||
}
|
||||
@@ -310,7 +304,7 @@ func (m *MockManager) StartExposeReaper(ctx context.Context) {
|
||||
}
|
||||
|
||||
// StartExposeReaper indicates an expected call of StartExposeReaper.
|
||||
func (mr *MockManagerMockRecorder) StartExposeReaper(ctx any) *gomock.Call {
|
||||
func (mr *MockManagerMockRecorder) StartExposeReaper(ctx interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StartExposeReaper", reflect.TypeOf((*MockManager)(nil).StartExposeReaper), ctx)
|
||||
}
|
||||
@@ -324,7 +318,7 @@ func (m *MockManager) StopServiceFromPeer(ctx context.Context, accountID, peerID
|
||||
}
|
||||
|
||||
// StopServiceFromPeer indicates an expected call of StopServiceFromPeer.
|
||||
func (mr *MockManagerMockRecorder) StopServiceFromPeer(ctx, accountID, peerID, serviceID any) *gomock.Call {
|
||||
func (mr *MockManagerMockRecorder) StopServiceFromPeer(ctx, accountID, peerID, serviceID interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StopServiceFromPeer", reflect.TypeOf((*MockManager)(nil).StopServiceFromPeer), ctx, accountID, peerID, serviceID)
|
||||
}
|
||||
@@ -339,7 +333,7 @@ func (m *MockManager) UpdateService(ctx context.Context, accountID, userID strin
|
||||
}
|
||||
|
||||
// UpdateService indicates an expected call of UpdateService.
|
||||
func (mr *MockManagerMockRecorder) UpdateService(ctx, accountID, userID, service any) *gomock.Call {
|
||||
func (mr *MockManagerMockRecorder) UpdateService(ctx, accountID, userID, service interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateService", reflect.TypeOf((*MockManager)(nil).UpdateService), ctx, accountID, userID, service)
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"go.uber.org/mock/gomock"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"time"
|
||||
|
||||
cachestore "github.com/eko/gocache/lib/v4/store"
|
||||
"go.uber.org/mock/gomock"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.opentelemetry.io/otel/metric/noop"
|
||||
|
||||
@@ -165,6 +165,10 @@ type AccessRestrictions struct {
|
||||
AllowedCountries []string `json:"allowed_countries,omitempty" gorm:"serializer:json"`
|
||||
BlockedCountries []string `json:"blocked_countries,omitempty" gorm:"serializer:json"`
|
||||
CrowdSecMode string `json:"crowdsec_mode,omitempty" gorm:"serializer:json"`
|
||||
// AllowMatch controls how the allowlists combine: "" or "all" require
|
||||
// matching every allowlist (AND), "any" requires matching at least one (OR).
|
||||
// Empty is treated as "all" for backward compatibility with existing records.
|
||||
AllowMatch string `json:"allow_match,omitempty" gorm:"serializer:json"`
|
||||
}
|
||||
|
||||
// Copy returns a deep copy of the AccessRestrictions.
|
||||
@@ -175,6 +179,7 @@ func (r AccessRestrictions) Copy() AccessRestrictions {
|
||||
AllowedCountries: slices.Clone(r.AllowedCountries),
|
||||
BlockedCountries: slices.Clone(r.BlockedCountries),
|
||||
CrowdSecMode: r.CrowdSecMode,
|
||||
AllowMatch: r.AllowMatch,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -808,13 +813,19 @@ func restrictionsFromAPI(r *api.AccessRestrictions) (AccessRestrictions, error)
|
||||
}
|
||||
res.CrowdSecMode = string(*r.CrowdsecMode)
|
||||
}
|
||||
if r.AllowMatch != nil {
|
||||
if !r.AllowMatch.Valid() {
|
||||
return AccessRestrictions{}, fmt.Errorf("invalid allow_match %q", *r.AllowMatch)
|
||||
}
|
||||
res.AllowMatch = string(*r.AllowMatch)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func restrictionsToAPI(r AccessRestrictions) *api.AccessRestrictions {
|
||||
if len(r.AllowedCIDRs) == 0 && len(r.BlockedCIDRs) == 0 &&
|
||||
len(r.AllowedCountries) == 0 && len(r.BlockedCountries) == 0 &&
|
||||
r.CrowdSecMode == "" {
|
||||
r.CrowdSecMode == "" && r.AllowMatch == "" {
|
||||
return nil
|
||||
}
|
||||
res := &api.AccessRestrictions{}
|
||||
@@ -834,13 +845,17 @@ func restrictionsToAPI(r AccessRestrictions) *api.AccessRestrictions {
|
||||
mode := api.AccessRestrictionsCrowdsecMode(r.CrowdSecMode)
|
||||
res.CrowdsecMode = &mode
|
||||
}
|
||||
if r.AllowMatch != "" {
|
||||
match := api.AccessRestrictionsAllowMatch(r.AllowMatch)
|
||||
res.AllowMatch = &match
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func restrictionsToProto(r AccessRestrictions) *proto.AccessRestrictions {
|
||||
if len(r.AllowedCIDRs) == 0 && len(r.BlockedCIDRs) == 0 &&
|
||||
len(r.AllowedCountries) == 0 && len(r.BlockedCountries) == 0 &&
|
||||
r.CrowdSecMode == "" {
|
||||
r.CrowdSecMode == "" && r.AllowMatch == "" {
|
||||
return nil
|
||||
}
|
||||
return &proto.AccessRestrictions{
|
||||
@@ -849,6 +864,7 @@ func restrictionsToProto(r AccessRestrictions) *proto.AccessRestrictions {
|
||||
AllowedCountries: r.AllowedCountries,
|
||||
BlockedCountries: r.BlockedCountries,
|
||||
CrowdsecMode: r.CrowdSecMode,
|
||||
AllowMatch: r.AllowMatch,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1242,10 +1258,22 @@ func validateCrowdSecMode(mode string) error {
|
||||
}
|
||||
}
|
||||
|
||||
func validateAllowMatch(mode string) error {
|
||||
switch mode {
|
||||
case "", "all", "any":
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("allow_match %q is invalid", mode)
|
||||
}
|
||||
}
|
||||
|
||||
func validateAccessRestrictions(r *AccessRestrictions) error {
|
||||
if err := validateCrowdSecMode(r.CrowdSecMode); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validateAllowMatch(r.AllowMatch); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(r.AllowedCIDRs) > maxCIDREntries {
|
||||
return fmt.Errorf("allowed_cidrs: exceeds maximum of %d entries", maxCIDREntries)
|
||||
|
||||
@@ -1302,6 +1302,65 @@ func TestValidate_Private_AcceptsClusterTargetWithAccessGroups(t *testing.T) {
|
||||
require.NoError(t, rp.Validate())
|
||||
}
|
||||
|
||||
func TestRestrictions_AllowMatch_RoundTrip(t *testing.T) {
|
||||
anyMatch := api.AccessRestrictionsAllowMatchAny
|
||||
apiIn := &api.AccessRestrictions{
|
||||
AllowedCidrs: &[]string{"203.0.113.0/24"},
|
||||
AllowedCountries: &[]string{"US"},
|
||||
AllowMatch: &anyMatch,
|
||||
}
|
||||
|
||||
model, err := restrictionsFromAPI(apiIn)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "any", model.AllowMatch)
|
||||
|
||||
apiOut := restrictionsToAPI(model)
|
||||
require.NotNil(t, apiOut.AllowMatch)
|
||||
assert.Equal(t, api.AccessRestrictionsAllowMatchAny, *apiOut.AllowMatch)
|
||||
|
||||
protoOut := restrictionsToProto(model)
|
||||
require.NotNil(t, protoOut)
|
||||
assert.Equal(t, "any", protoOut.AllowMatch)
|
||||
}
|
||||
|
||||
func TestRestrictions_AllowMatch_EmptyDefaultsToAll(t *testing.T) {
|
||||
// A stored record with no allow_match (existing services) stays empty and
|
||||
// must not surface a value on the API, preserving AND behavior downstream.
|
||||
model, err := restrictionsFromAPI(&api.AccessRestrictions{
|
||||
AllowedCidrs: &[]string{"203.0.113.0/24"},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, model.AllowMatch, "unset allow_match stays empty")
|
||||
|
||||
apiOut := restrictionsToAPI(model)
|
||||
require.NotNil(t, apiOut)
|
||||
assert.Nil(t, apiOut.AllowMatch, "empty allow_match is omitted from the API response")
|
||||
}
|
||||
|
||||
func TestRestrictions_AllowMatchOnly_Preserved(t *testing.T) {
|
||||
// allow_match set without any list must not be dropped by the emptiness
|
||||
// guards, so it round-trips through both the API and proto conversions.
|
||||
model := AccessRestrictions{AllowMatch: "any"}
|
||||
|
||||
apiOut := restrictionsToAPI(model)
|
||||
require.NotNil(t, apiOut, "allow-match-only restriction must not be omitted from the API response")
|
||||
require.NotNil(t, apiOut.AllowMatch)
|
||||
assert.Equal(t, api.AccessRestrictionsAllowMatchAny, *apiOut.AllowMatch)
|
||||
|
||||
protoOut := restrictionsToProto(model)
|
||||
require.NotNil(t, protoOut, "allow-match-only restriction must not be omitted from the proto output")
|
||||
assert.Equal(t, "any", protoOut.AllowMatch)
|
||||
}
|
||||
|
||||
func TestValidate_RejectsInvalidAllowMatch(t *testing.T) {
|
||||
rp := validProxy()
|
||||
rp.Restrictions = AccessRestrictions{
|
||||
AllowedCIDRs: []string{"203.0.113.0/24"},
|
||||
AllowMatch: "sometimes",
|
||||
}
|
||||
assert.ErrorContains(t, rp.Validate(), "allow_match")
|
||||
}
|
||||
|
||||
func TestValidate_Private_RejectsNonHTTPMode(t *testing.T) {
|
||||
rp := validProxy()
|
||||
rp.Private = true
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"go.uber.org/mock/gomock"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"go.uber.org/mock/gomock"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"go.uber.org/mock/gomock"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
nbconfig "github.com/netbirdio/netbird/management/internals/server/config"
|
||||
|
||||
@@ -311,7 +311,7 @@ func buildJWTConfig(config *nbconfig.HttpServerConfig, deviceFlowConfig *nbconfi
|
||||
|
||||
return &proto.JWTConfig{
|
||||
Issuer: issuer,
|
||||
Audience: audience, //nolint:staticcheck
|
||||
Audience: audience,
|
||||
Audiences: audiences,
|
||||
KeysLocation: keysLocation,
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"go.uber.org/mock/gomock"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/grpc/codes"
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"go.uber.org/mock/gomock"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/grpc"
|
||||
|
||||
@@ -1140,7 +1140,7 @@ func (s *Server) GetDeviceAuthorizationFlow(ctx context.Context, req *proto.Encr
|
||||
Provider: proto.DeviceAuthorizationFlowProvider(provider),
|
||||
ProviderConfig: &proto.ProviderConfig{
|
||||
ClientID: s.config.DeviceAuthorizationFlow.ProviderConfig.ClientID,
|
||||
ClientSecret: s.config.DeviceAuthorizationFlow.ProviderConfig.ClientSecret, //nolint:staticcheck
|
||||
ClientSecret: s.config.DeviceAuthorizationFlow.ProviderConfig.ClientSecret,
|
||||
Domain: s.config.DeviceAuthorizationFlow.ProviderConfig.Domain,
|
||||
Audience: s.config.DeviceAuthorizationFlow.ProviderConfig.Audience,
|
||||
DeviceAuthEndpoint: s.config.DeviceAuthorizationFlow.ProviderConfig.DeviceAuthEndpoint,
|
||||
@@ -1211,7 +1211,7 @@ func (s *Server) GetPKCEAuthorizationFlow(ctx context.Context, req *proto.Encryp
|
||||
ProviderConfig: &proto.ProviderConfig{
|
||||
Audience: s.config.PKCEAuthorizationFlow.ProviderConfig.Audience,
|
||||
ClientID: s.config.PKCEAuthorizationFlow.ProviderConfig.ClientID,
|
||||
ClientSecret: s.config.PKCEAuthorizationFlow.ProviderConfig.ClientSecret, //nolint:staticcheck
|
||||
ClientSecret: s.config.PKCEAuthorizationFlow.ProviderConfig.ClientSecret,
|
||||
TokenEndpoint: s.config.PKCEAuthorizationFlow.ProviderConfig.TokenEndpoint,
|
||||
AuthorizationEndpoint: s.config.PKCEAuthorizationFlow.ProviderConfig.AuthorizationEndpoint,
|
||||
Scope: s.config.PKCEAuthorizationFlow.ProviderConfig.Scope,
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"go.uber.org/mock/gomock"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/grpc"
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"go.uber.org/mock/gomock"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/netbirdio/netbird/management/internals/controllers/network_map"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package account
|
||||
|
||||
//go:generate go tool mockgen -package account -destination=manager_mock.go -source=./manager.go -build_flags=-mod=mod
|
||||
//go:generate go run github.com/golang/mock/mockgen -package account -destination=manager_mock.go -source=./manager.go -build_flags=-mod=mod
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -14,7 +14,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"go.uber.org/mock/gomock"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/prometheus/client_golang/prometheus/push"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"go.uber.org/mock/gomock"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
nbdns "github.com/netbirdio/netbird/dns"
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"go.uber.org/mock/gomock"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"go.uber.org/mock/gomock"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"net/mail"
|
||||
"testing"
|
||||
|
||||
"go.uber.org/mock/gomock"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
@@ -13,8 +13,9 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/gorilla/mux"
|
||||
"go.uber.org/mock/gomock"
|
||||
ugomock "go.uber.org/mock/gomock"
|
||||
"golang.org/x/exp/maps"
|
||||
|
||||
"github.com/netbirdio/netbird/management/internals/controllers/network_map"
|
||||
@@ -105,7 +106,7 @@ func initTestMetaData(t *testing.T, peers ...*nbpeer.Peer) *Handler {
|
||||
},
|
||||
}
|
||||
|
||||
ctrl := gomock.NewController(t)
|
||||
ctrl := ugomock.NewController(t)
|
||||
|
||||
networkMapController := network_map.NewMockController(ctrl)
|
||||
networkMapController.EXPECT().
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"go.uber.org/mock/gomock"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"go.uber.org/mock/gomock"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"go.uber.org/mock/gomock"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"go.uber.org/mock/gomock"
|
||||
"github.com/golang/mock/gomock"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"go.uber.org/mock/gomock"
|
||||
"github.com/golang/mock/gomock"
|
||||
pb "github.com/golang/protobuf/proto" //nolint
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"go.uber.org/mock/gomock"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"go.uber.org/mock/gomock"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
reverseproxy "github.com/netbirdio/netbird/management/internals/modules/reverseproxy/service"
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"go.uber.org/mock/gomock"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/rs/xid"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package permissions
|
||||
|
||||
//go:generate go tool mockgen -package permissions -destination=manager_mock.go -source=./manager.go -build_flags=-mod=mod
|
||||
//go:generate go run github.com/golang/mock/mockgen -package permissions -destination=manager_mock.go -source=./manager.go -build_flags=-mod=mod
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: ./manager.go
|
||||
//
|
||||
// Generated by this command:
|
||||
//
|
||||
// mockgen -package permissions -destination=manager_mock.go -source=./manager.go -build_flags=-mod=mod
|
||||
//
|
||||
|
||||
// Package permissions is a generated GoMock package.
|
||||
package permissions
|
||||
@@ -13,19 +8,18 @@ import (
|
||||
context "context"
|
||||
reflect "reflect"
|
||||
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
account "github.com/netbirdio/netbird/management/server/account"
|
||||
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"
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
// MockManager is a mock of Manager interface.
|
||||
type MockManager struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockManagerMockRecorder
|
||||
isgomock struct{}
|
||||
}
|
||||
|
||||
// MockManagerMockRecorder is the mock recorder for MockManager.
|
||||
@@ -55,7 +49,7 @@ func (m *MockManager) GetPermissionsByRole(ctx context.Context, role types.UserR
|
||||
}
|
||||
|
||||
// GetPermissionsByRole indicates an expected call of GetPermissionsByRole.
|
||||
func (mr *MockManagerMockRecorder) GetPermissionsByRole(ctx, role any) *gomock.Call {
|
||||
func (mr *MockManagerMockRecorder) GetPermissionsByRole(ctx, role interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPermissionsByRole", reflect.TypeOf((*MockManager)(nil).GetPermissionsByRole), ctx, role)
|
||||
}
|
||||
@@ -67,7 +61,7 @@ func (m *MockManager) SetAccountManager(accountManager account.Manager) {
|
||||
}
|
||||
|
||||
// SetAccountManager indicates an expected call of SetAccountManager.
|
||||
func (mr *MockManagerMockRecorder) SetAccountManager(accountManager any) *gomock.Call {
|
||||
func (mr *MockManagerMockRecorder) SetAccountManager(accountManager interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetAccountManager", reflect.TypeOf((*MockManager)(nil).SetAccountManager), accountManager)
|
||||
}
|
||||
@@ -82,7 +76,7 @@ func (m *MockManager) ValidateAccountAccess(ctx context.Context, accountID strin
|
||||
}
|
||||
|
||||
// ValidateAccountAccess indicates an expected call of ValidateAccountAccess.
|
||||
func (mr *MockManagerMockRecorder) ValidateAccountAccess(ctx, accountID, user, allowOwnerAndAdmin any) *gomock.Call {
|
||||
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)
|
||||
}
|
||||
@@ -96,7 +90,7 @@ func (m *MockManager) ValidateRoleModuleAccess(ctx context.Context, accountID st
|
||||
}
|
||||
|
||||
// ValidateRoleModuleAccess indicates an expected call of ValidateRoleModuleAccess.
|
||||
func (mr *MockManagerMockRecorder) ValidateRoleModuleAccess(ctx, accountID, role, module, operation any) *gomock.Call {
|
||||
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)
|
||||
}
|
||||
@@ -112,7 +106,7 @@ func (m *MockManager) ValidateUserPermissions(ctx context.Context, accountID, us
|
||||
}
|
||||
|
||||
// ValidateUserPermissions indicates an expected call of ValidateUserPermissions.
|
||||
func (mr *MockManagerMockRecorder) ValidateUserPermissions(ctx, accountID, userID, module, operation any) *gomock.Call {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"go.uber.org/mock/gomock"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/rs/xid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package settings
|
||||
|
||||
//go:generate go tool mockgen -package settings -destination=manager_mock.go -source=./manager.go -build_flags=-mod=mod
|
||||
//go:generate go run github.com/golang/mock/mockgen -package settings -destination=manager_mock.go -source=./manager.go -build_flags=-mod=mod
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: ./manager.go
|
||||
//
|
||||
// Generated by this command:
|
||||
//
|
||||
// mockgen -package settings -destination=manager_mock.go -source=./manager.go -build_flags=-mod=mod
|
||||
//
|
||||
|
||||
// Package settings is a generated GoMock package.
|
||||
package settings
|
||||
@@ -14,16 +9,15 @@ import (
|
||||
netip "net/netip"
|
||||
reflect "reflect"
|
||||
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
extra_settings "github.com/netbirdio/netbird/management/server/integrations/extra_settings"
|
||||
types "github.com/netbirdio/netbird/management/server/types"
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
// MockManager is a mock of Manager interface.
|
||||
type MockManager struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockManagerMockRecorder
|
||||
isgomock struct{}
|
||||
}
|
||||
|
||||
// MockManagerMockRecorder is the mock recorder for MockManager.
|
||||
@@ -43,22 +37,6 @@ func (m *MockManager) EXPECT() *MockManagerMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// GetEffectiveNetworkRanges mocks base method.
|
||||
func (m *MockManager) GetEffectiveNetworkRanges(ctx context.Context, accountID string) (netip.Prefix, netip.Prefix, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetEffectiveNetworkRanges", ctx, accountID)
|
||||
ret0, _ := ret[0].(netip.Prefix)
|
||||
ret1, _ := ret[1].(netip.Prefix)
|
||||
ret2, _ := ret[2].(error)
|
||||
return ret0, ret1, ret2
|
||||
}
|
||||
|
||||
// GetEffectiveNetworkRanges indicates an expected call of GetEffectiveNetworkRanges.
|
||||
func (mr *MockManagerMockRecorder) GetEffectiveNetworkRanges(ctx, accountID any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetEffectiveNetworkRanges", reflect.TypeOf((*MockManager)(nil).GetEffectiveNetworkRanges), ctx, accountID)
|
||||
}
|
||||
|
||||
// GetExtraSettings mocks base method.
|
||||
func (m *MockManager) GetExtraSettings(ctx context.Context, accountID string) (*types.ExtraSettings, error) {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -69,7 +47,7 @@ func (m *MockManager) GetExtraSettings(ctx context.Context, accountID string) (*
|
||||
}
|
||||
|
||||
// GetExtraSettings indicates an expected call of GetExtraSettings.
|
||||
func (mr *MockManagerMockRecorder) GetExtraSettings(ctx, accountID any) *gomock.Call {
|
||||
func (mr *MockManagerMockRecorder) GetExtraSettings(ctx, accountID interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetExtraSettings", reflect.TypeOf((*MockManager)(nil).GetExtraSettings), ctx, accountID)
|
||||
}
|
||||
@@ -98,7 +76,7 @@ func (m *MockManager) GetSettings(ctx context.Context, accountID, userID string)
|
||||
}
|
||||
|
||||
// GetSettings indicates an expected call of GetSettings.
|
||||
func (mr *MockManagerMockRecorder) GetSettings(ctx, accountID, userID any) *gomock.Call {
|
||||
func (mr *MockManagerMockRecorder) GetSettings(ctx, accountID, userID interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSettings", reflect.TypeOf((*MockManager)(nil).GetSettings), ctx, accountID, userID)
|
||||
}
|
||||
@@ -113,7 +91,23 @@ func (m *MockManager) UpdateExtraSettings(ctx context.Context, accountID, userID
|
||||
}
|
||||
|
||||
// UpdateExtraSettings indicates an expected call of UpdateExtraSettings.
|
||||
func (mr *MockManagerMockRecorder) UpdateExtraSettings(ctx, accountID, userID, extraSettings any) *gomock.Call {
|
||||
func (mr *MockManagerMockRecorder) UpdateExtraSettings(ctx, accountID, userID, extraSettings interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateExtraSettings", reflect.TypeOf((*MockManager)(nil).UpdateExtraSettings), ctx, accountID, userID, extraSettings)
|
||||
}
|
||||
|
||||
// GetEffectiveNetworkRanges mocks base method.
|
||||
func (m *MockManager) GetEffectiveNetworkRanges(ctx context.Context, accountID string) (netip.Prefix, netip.Prefix, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetEffectiveNetworkRanges", ctx, accountID)
|
||||
ret0, _ := ret[0].(netip.Prefix)
|
||||
ret1, _ := ret[1].(netip.Prefix)
|
||||
ret2, _ := ret[2].(error)
|
||||
return ret0, ret1, ret2
|
||||
}
|
||||
|
||||
// GetEffectiveNetworkRanges indicates an expected call of GetEffectiveNetworkRanges.
|
||||
func (mr *MockManagerMockRecorder) GetEffectiveNetworkRanges(ctx, accountID interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetEffectiveNetworkRanges", reflect.TypeOf((*MockManager)(nil).GetEffectiveNetworkRanges), ctx, accountID)
|
||||
}
|
||||
|
||||
@@ -133,3 +133,42 @@ func TestSqlStore_GetAccount_ServiceTargetOptionsRoundtrip(t *testing.T) {
|
||||
assert.True(t, tg.Options.DisableAccessLog, "options disable access log")
|
||||
})
|
||||
}
|
||||
|
||||
func TestSqlStore_GetAccount_ServiceRestrictionsRoundtrip(t *testing.T) {
|
||||
if os.Getenv("CI") == "true" && (runtime.GOOS == "darwin" || runtime.GOOS == "windows") {
|
||||
t.Skip("skip CI tests on darwin and windows")
|
||||
}
|
||||
|
||||
runTestForAllEngines(t, "", func(t *testing.T, store Store) {
|
||||
ctx := context.Background()
|
||||
account := newAccountWithId(ctx, "account_svc_restrictions", "testuser", "")
|
||||
require.NoError(t, store.SaveAccount(ctx, account))
|
||||
|
||||
svc := &rpservice.Service{
|
||||
ID: "svc-restrictions",
|
||||
AccountID: account.Id,
|
||||
Name: "restricted-svc",
|
||||
Domain: "restricted.example",
|
||||
Enabled: true,
|
||||
Mode: rpservice.ModeHTTP,
|
||||
Restrictions: rpservice.AccessRestrictions{
|
||||
AllowedCIDRs: []string{"203.0.113.0/24"},
|
||||
AllowedCountries: []string{"US"},
|
||||
AllowMatch: "any",
|
||||
},
|
||||
}
|
||||
require.NoError(t, store.CreateService(ctx, svc))
|
||||
|
||||
loaded, err := store.GetAccount(ctx, account.Id)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, loaded.Services, 1)
|
||||
|
||||
// Restrictions are stored as a JSON blob; confirm the whole struct,
|
||||
// including allow_match, survives the read path (Postgres pgx path
|
||||
// included via runTestForAllEngines).
|
||||
got := loaded.Services[0].Restrictions
|
||||
assert.Equal(t, []string{"203.0.113.0/24"}, got.AllowedCIDRs)
|
||||
assert.Equal(t, []string{"US"}, got.AllowedCountries)
|
||||
assert.Equal(t, "any", got.AllowMatch)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package store
|
||||
|
||||
//go:generate go tool mockgen -package store -destination=store_mock.go -source=./store.go -build_flags=-mod=mod
|
||||
//go:generate go run github.com/golang/mock/mockgen -package store -destination=store_mock.go -source=./store.go -build_flags=-mod=mod
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user