mirror of
https://github.com/netbirdio/netbird.git
synced 2026-10-28 07:19:08 +01:00
Merge branch 'main' into feature/add-serial-to-proxy
This commit is contained in:
@@ -0,0 +1,194 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/client/proto"
|
||||||
|
"github.com/netbirdio/netbird/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
var pinRegexp = regexp.MustCompile(`^\d{6}$`)
|
||||||
|
|
||||||
|
var (
|
||||||
|
exposePin string
|
||||||
|
exposePassword string
|
||||||
|
exposeUserGroups []string
|
||||||
|
exposeDomain string
|
||||||
|
exposeNamePrefix string
|
||||||
|
exposeProtocol string
|
||||||
|
)
|
||||||
|
|
||||||
|
var exposeCmd = &cobra.Command{
|
||||||
|
Use: "expose <port>",
|
||||||
|
Short: "Expose a local port via the NetBird reverse proxy",
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
Example: "netbird expose --with-password safe-pass 8080",
|
||||||
|
RunE: exposeFn,
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
exposeCmd.Flags().StringVar(&exposePin, "with-pin", "", "Protect the exposed service with a 6-digit PIN (e.g. --with-pin 123456)")
|
||||||
|
exposeCmd.Flags().StringVar(&exposePassword, "with-password", "", "Protect the exposed service with a password (e.g. --with-password my-secret)")
|
||||||
|
exposeCmd.Flags().StringSliceVar(&exposeUserGroups, "with-user-groups", nil, "Restrict access to specific user groups with SSO (e.g. --with-user-groups devops,Backend)")
|
||||||
|
exposeCmd.Flags().StringVar(&exposeDomain, "with-custom-domain", "", "Custom domain for the exposed service, must be configured to your account (e.g. --with-custom-domain myapp.example.com)")
|
||||||
|
exposeCmd.Flags().StringVar(&exposeNamePrefix, "with-name-prefix", "", "Prefix for the generated service name (e.g. --with-name-prefix my-app)")
|
||||||
|
exposeCmd.Flags().StringVar(&exposeProtocol, "protocol", "http", "Protocol to use, http/https is supported (e.g. --protocol http)")
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateExposeFlags(cmd *cobra.Command, portStr string) (uint64, error) {
|
||||||
|
port, err := strconv.ParseUint(portStr, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("invalid port number: %s", portStr)
|
||||||
|
}
|
||||||
|
if port == 0 || port > 65535 {
|
||||||
|
return 0, fmt.Errorf("invalid port number: must be between 1 and 65535")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isProtocolValid(exposeProtocol) {
|
||||||
|
return 0, fmt.Errorf("unsupported protocol %q: only 'http' or 'https' are supported", exposeProtocol)
|
||||||
|
}
|
||||||
|
|
||||||
|
if exposePin != "" && !pinRegexp.MatchString(exposePin) {
|
||||||
|
return 0, fmt.Errorf("invalid pin: must be exactly 6 digits")
|
||||||
|
}
|
||||||
|
|
||||||
|
if cmd.Flags().Changed("with-password") && exposePassword == "" {
|
||||||
|
return 0, fmt.Errorf("password cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
if cmd.Flags().Changed("with-user-groups") && len(exposeUserGroups) == 0 {
|
||||||
|
return 0, fmt.Errorf("user groups cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
return port, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isProtocolValid(exposeProtocol string) bool {
|
||||||
|
return strings.ToLower(exposeProtocol) == "http" || strings.ToLower(exposeProtocol) == "https"
|
||||||
|
}
|
||||||
|
|
||||||
|
func exposeFn(cmd *cobra.Command, args []string) error {
|
||||||
|
SetFlagsFromEnvVars(rootCmd)
|
||||||
|
|
||||||
|
if err := util.InitLog(logLevel, util.LogConsole); err != nil {
|
||||||
|
log.Errorf("failed initializing log %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.Root().SilenceUsage = false
|
||||||
|
|
||||||
|
port, err := validateExposeFlags(cmd, args[0])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.Root().SilenceUsage = true
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(cmd.Context())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
sigCh := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
go func() {
|
||||||
|
<-sigCh
|
||||||
|
cancel()
|
||||||
|
}()
|
||||||
|
|
||||||
|
conn, err := DialClientGRPCServer(ctx, daemonAddr)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("connect to daemon: %w", err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if err := conn.Close(); err != nil {
|
||||||
|
log.Debugf("failed to close daemon connection: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
client := proto.NewDaemonServiceClient(conn)
|
||||||
|
|
||||||
|
protocol, err := toExposeProtocol(exposeProtocol)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
stream, err := client.ExposeService(ctx, &proto.ExposeServiceRequest{
|
||||||
|
Port: uint32(port),
|
||||||
|
Protocol: protocol,
|
||||||
|
Pin: exposePin,
|
||||||
|
Password: exposePassword,
|
||||||
|
UserGroups: exposeUserGroups,
|
||||||
|
Domain: exposeDomain,
|
||||||
|
NamePrefix: exposeNamePrefix,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("expose service: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := handleExposeReady(cmd, stream, port); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return waitForExposeEvents(cmd, ctx, stream)
|
||||||
|
}
|
||||||
|
|
||||||
|
func toExposeProtocol(exposeProtocol string) (proto.ExposeProtocol, error) {
|
||||||
|
switch strings.ToLower(exposeProtocol) {
|
||||||
|
case "http":
|
||||||
|
return proto.ExposeProtocol_EXPOSE_HTTP, nil
|
||||||
|
case "https":
|
||||||
|
return proto.ExposeProtocol_EXPOSE_HTTPS, nil
|
||||||
|
default:
|
||||||
|
return 0, fmt.Errorf("unsupported protocol %q: only 'http' or 'https' are supported", exposeProtocol)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleExposeReady(cmd *cobra.Command, stream proto.DaemonService_ExposeServiceClient, port uint64) error {
|
||||||
|
event, err := stream.Recv()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("receive expose event: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch e := event.Event.(type) {
|
||||||
|
case *proto.ExposeServiceEvent_Ready:
|
||||||
|
cmd.Println("Service exposed successfully!")
|
||||||
|
cmd.Printf(" Name: %s\n", e.Ready.ServiceName)
|
||||||
|
cmd.Printf(" URL: %s\n", e.Ready.ServiceUrl)
|
||||||
|
cmd.Printf(" Domain: %s\n", e.Ready.Domain)
|
||||||
|
cmd.Printf(" Protocol: %s\n", exposeProtocol)
|
||||||
|
cmd.Printf(" Port: %d\n", port)
|
||||||
|
cmd.Println()
|
||||||
|
cmd.Println("Press Ctrl+C to stop exposing.")
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unexpected expose event: %T", event.Event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func waitForExposeEvents(cmd *cobra.Command, ctx context.Context, stream proto.DaemonService_ExposeServiceClient) error {
|
||||||
|
for {
|
||||||
|
_, err := stream.Recv()
|
||||||
|
if err != nil {
|
||||||
|
if ctx.Err() != nil {
|
||||||
|
cmd.Println("\nService stopped.")
|
||||||
|
//nolint:nilerr
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if errors.Is(err, io.EOF) {
|
||||||
|
return fmt.Errorf("connection to daemon closed unexpectedly")
|
||||||
|
}
|
||||||
|
return fmt.Errorf("stream error: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -144,6 +144,7 @@ func init() {
|
|||||||
rootCmd.AddCommand(forwardingRulesCmd)
|
rootCmd.AddCommand(forwardingRulesCmd)
|
||||||
rootCmd.AddCommand(debugCmd)
|
rootCmd.AddCommand(debugCmd)
|
||||||
rootCmd.AddCommand(profileCmd)
|
rootCmd.AddCommand(profileCmd)
|
||||||
|
rootCmd.AddCommand(exposeCmd)
|
||||||
|
|
||||||
networksCMD.AddCommand(routesListCmd)
|
networksCMD.AddCommand(routesListCmd)
|
||||||
networksCMD.AddCommand(routesSelectCmd, routesDeselectCmd)
|
networksCMD.AddCommand(routesSelectCmd, routesDeselectCmd)
|
||||||
|
|||||||
@@ -5,20 +5,18 @@ package configurer
|
|||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"golang.zx2c4.com/wireguard/ipc"
|
"golang.zx2c4.com/wireguard/ipc"
|
||||||
)
|
)
|
||||||
|
|
||||||
func openUAPI(deviceName string) (net.Listener, error) {
|
func openUAPI(deviceName string) (net.Listener, error) {
|
||||||
uapiSock, err := ipc.UAPIOpen(deviceName)
|
uapiSock, err := ipc.UAPIOpen(deviceName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed to open uapi socket: %v", err)
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
listener, err := ipc.UAPIListen(deviceName, uapiSock)
|
listener, err := ipc.UAPIListen(deviceName, uapiSock)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed to listen on uapi socket: %v", err)
|
_ = uapiSock.Close()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -54,6 +54,14 @@ func NewUSPConfigurer(device *device.Device, deviceName string, activityRecorder
|
|||||||
return wgCfg
|
return wgCfg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewUSPConfigurerNoUAPI(device *device.Device, deviceName string, activityRecorder *bind.ActivityRecorder) *WGUSPConfigurer {
|
||||||
|
return &WGUSPConfigurer{
|
||||||
|
device: device,
|
||||||
|
deviceName: deviceName,
|
||||||
|
activityRecorder: activityRecorder,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (c *WGUSPConfigurer) ConfigureInterface(privateKey string, port int) error {
|
func (c *WGUSPConfigurer) ConfigureInterface(privateKey string, port int) error {
|
||||||
log.Debugf("adding Wireguard private key")
|
log.Debugf("adding Wireguard private key")
|
||||||
key, err := wgtypes.ParseKey(privateKey)
|
key, err := wgtypes.ParseKey(privateKey)
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ func (t *TunNetstackDevice) create() (WGConfigurer, error) {
|
|||||||
device.NewLogger(wgLogLevel(), "[netbird] "),
|
device.NewLogger(wgLogLevel(), "[netbird] "),
|
||||||
)
|
)
|
||||||
|
|
||||||
t.configurer = configurer.NewUSPConfigurer(t.device, t.name, t.bind.ActivityRecorder())
|
t.configurer = configurer.NewUSPConfigurerNoUAPI(t.device, t.name, t.bind.ActivityRecorder())
|
||||||
err = t.configurer.ConfigureInterface(t.key, t.port)
|
err = t.configurer.ConfigureInterface(t.key, t.port)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if cErr := tunIface.Close(); cErr != nil {
|
if cErr := tunIface.Close(); cErr != nil {
|
||||||
|
|||||||
@@ -277,7 +277,7 @@ func (r *registryConfigurator) addDNSMatchPolicy(domains []string, ip netip.Addr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Infof("added %d NRPT rules for %d domains. Domain list: %v", ruleIndex, len(domains), domains)
|
log.Infof("added %d NRPT rules for %d domains", ruleIndex, len(domains))
|
||||||
return ruleIndex, nil
|
return ruleIndex, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import (
|
|||||||
"github.com/netbirdio/netbird/client/internal/dns"
|
"github.com/netbirdio/netbird/client/internal/dns"
|
||||||
dnsconfig "github.com/netbirdio/netbird/client/internal/dns/config"
|
dnsconfig "github.com/netbirdio/netbird/client/internal/dns/config"
|
||||||
"github.com/netbirdio/netbird/client/internal/dnsfwd"
|
"github.com/netbirdio/netbird/client/internal/dnsfwd"
|
||||||
|
"github.com/netbirdio/netbird/client/internal/expose"
|
||||||
"github.com/netbirdio/netbird/client/internal/ingressgw"
|
"github.com/netbirdio/netbird/client/internal/ingressgw"
|
||||||
"github.com/netbirdio/netbird/client/internal/netflow"
|
"github.com/netbirdio/netbird/client/internal/netflow"
|
||||||
nftypes "github.com/netbirdio/netbird/client/internal/netflow/types"
|
nftypes "github.com/netbirdio/netbird/client/internal/netflow/types"
|
||||||
@@ -53,13 +54,11 @@ import (
|
|||||||
"github.com/netbirdio/netbird/client/internal/updatemanager"
|
"github.com/netbirdio/netbird/client/internal/updatemanager"
|
||||||
"github.com/netbirdio/netbird/client/jobexec"
|
"github.com/netbirdio/netbird/client/jobexec"
|
||||||
cProto "github.com/netbirdio/netbird/client/proto"
|
cProto "github.com/netbirdio/netbird/client/proto"
|
||||||
"github.com/netbirdio/netbird/shared/management/domain"
|
|
||||||
semaphoregroup "github.com/netbirdio/netbird/util/semaphore-group"
|
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/system"
|
"github.com/netbirdio/netbird/client/system"
|
||||||
nbdns "github.com/netbirdio/netbird/dns"
|
nbdns "github.com/netbirdio/netbird/dns"
|
||||||
"github.com/netbirdio/netbird/route"
|
"github.com/netbirdio/netbird/route"
|
||||||
mgm "github.com/netbirdio/netbird/shared/management/client"
|
mgm "github.com/netbirdio/netbird/shared/management/client"
|
||||||
|
"github.com/netbirdio/netbird/shared/management/domain"
|
||||||
mgmProto "github.com/netbirdio/netbird/shared/management/proto"
|
mgmProto "github.com/netbirdio/netbird/shared/management/proto"
|
||||||
auth "github.com/netbirdio/netbird/shared/relay/auth/hmac"
|
auth "github.com/netbirdio/netbird/shared/relay/auth/hmac"
|
||||||
relayClient "github.com/netbirdio/netbird/shared/relay/client"
|
relayClient "github.com/netbirdio/netbird/shared/relay/client"
|
||||||
@@ -75,7 +74,6 @@ import (
|
|||||||
const (
|
const (
|
||||||
PeerConnectionTimeoutMax = 45000 // ms
|
PeerConnectionTimeoutMax = 45000 // ms
|
||||||
PeerConnectionTimeoutMin = 30000 // ms
|
PeerConnectionTimeoutMin = 30000 // ms
|
||||||
connInitLimit = 200
|
|
||||||
disableAutoUpdate = "disabled"
|
disableAutoUpdate = "disabled"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -208,7 +206,6 @@ type Engine struct {
|
|||||||
syncRespMux sync.RWMutex
|
syncRespMux sync.RWMutex
|
||||||
persistSyncResponse bool
|
persistSyncResponse bool
|
||||||
latestSyncResponse *mgmProto.SyncResponse
|
latestSyncResponse *mgmProto.SyncResponse
|
||||||
connSemaphore *semaphoregroup.SemaphoreGroup
|
|
||||||
flowManager nftypes.FlowManager
|
flowManager nftypes.FlowManager
|
||||||
|
|
||||||
// auto-update
|
// auto-update
|
||||||
@@ -224,6 +221,8 @@ type Engine struct {
|
|||||||
|
|
||||||
jobExecutor *jobexec.Executor
|
jobExecutor *jobexec.Executor
|
||||||
jobExecutorWG sync.WaitGroup
|
jobExecutorWG sync.WaitGroup
|
||||||
|
|
||||||
|
exposeManager *expose.Manager
|
||||||
}
|
}
|
||||||
|
|
||||||
// Peer is an instance of the Connection Peer
|
// Peer is an instance of the Connection Peer
|
||||||
@@ -266,7 +265,6 @@ func NewEngine(
|
|||||||
statusRecorder: statusRecorder,
|
statusRecorder: statusRecorder,
|
||||||
stateManager: stateManager,
|
stateManager: stateManager,
|
||||||
checks: checks,
|
checks: checks,
|
||||||
connSemaphore: semaphoregroup.NewSemaphoreGroup(connInitLimit),
|
|
||||||
probeStunTurn: relay.NewStunTurnProbe(relay.DefaultCacheTTL),
|
probeStunTurn: relay.NewStunTurnProbe(relay.DefaultCacheTTL),
|
||||||
jobExecutor: jobexec.NewExecutor(),
|
jobExecutor: jobexec.NewExecutor(),
|
||||||
}
|
}
|
||||||
@@ -419,6 +417,7 @@ func (e *Engine) Start(netbirdConfig *mgmProto.NetbirdConfig, mgmtURL *url.URL)
|
|||||||
e.cancel()
|
e.cancel()
|
||||||
}
|
}
|
||||||
e.ctx, e.cancel = context.WithCancel(e.clientCtx)
|
e.ctx, e.cancel = context.WithCancel(e.clientCtx)
|
||||||
|
e.exposeManager = expose.NewManager(e.ctx, e.mgmClient)
|
||||||
|
|
||||||
wgIface, err := e.newWgIface()
|
wgIface, err := e.newWgIface()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -801,7 +800,7 @@ func (e *Engine) handleAutoUpdateVersion(autoUpdateSettings *mgmProto.AutoUpdate
|
|||||||
|
|
||||||
disabled := autoUpdateSettings.Version == disableAutoUpdate
|
disabled := autoUpdateSettings.Version == disableAutoUpdate
|
||||||
|
|
||||||
// Stop and cleanup if disabled
|
// stop and cleanup if disabled
|
||||||
if e.updateManager != nil && disabled {
|
if e.updateManager != nil && disabled {
|
||||||
log.Infof("auto-update is disabled, stopping update manager")
|
log.Infof("auto-update is disabled, stopping update manager")
|
||||||
e.updateManager.Stop()
|
e.updateManager.Stop()
|
||||||
@@ -1539,7 +1538,6 @@ func (e *Engine) createPeerConn(pubKey string, allowedIPs []netip.Prefix, agentV
|
|||||||
IFaceDiscover: e.mobileDep.IFaceDiscover,
|
IFaceDiscover: e.mobileDep.IFaceDiscover,
|
||||||
RelayManager: e.relayManager,
|
RelayManager: e.relayManager,
|
||||||
SrWatcher: e.srWatcher,
|
SrWatcher: e.srWatcher,
|
||||||
Semaphore: e.connSemaphore,
|
|
||||||
}
|
}
|
||||||
peerConn, err := peer.NewConn(config, serviceDependencies)
|
peerConn, err := peer.NewConn(config, serviceDependencies)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1824,11 +1822,18 @@ func (e *Engine) GetRouteManager() routemanager.Manager {
|
|||||||
return e.routeManager
|
return e.routeManager
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetFirewallManager returns the firewall manager
|
// GetFirewallManager returns the firewall manager.
|
||||||
func (e *Engine) GetFirewallManager() firewallManager.Manager {
|
func (e *Engine) GetFirewallManager() firewallManager.Manager {
|
||||||
return e.firewall
|
return e.firewall
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetExposeManager returns the expose session manager.
|
||||||
|
func (e *Engine) GetExposeManager() *expose.Manager {
|
||||||
|
e.syncMsgMux.Lock()
|
||||||
|
defer e.syncMsgMux.Unlock()
|
||||||
|
return e.exposeManager
|
||||||
|
}
|
||||||
|
|
||||||
func findIPFromInterfaceName(ifaceName string) (net.IP, error) {
|
func findIPFromInterfaceName(ifaceName string) (net.IP, error) {
|
||||||
iface, err := net.InterfaceByName(ifaceName)
|
iface, err := net.InterfaceByName(ifaceName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -0,0 +1,95 @@
|
|||||||
|
package expose
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
mgm "github.com/netbirdio/netbird/shared/management/client"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
const renewTimeout = 10 * time.Second
|
||||||
|
|
||||||
|
// Response holds the response from exposing a service.
|
||||||
|
type Response struct {
|
||||||
|
ServiceName string
|
||||||
|
ServiceURL string
|
||||||
|
Domain string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Request struct {
|
||||||
|
NamePrefix string
|
||||||
|
Domain string
|
||||||
|
Port uint16
|
||||||
|
Protocol int
|
||||||
|
Pin string
|
||||||
|
Password string
|
||||||
|
UserGroups []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ManagementClient interface {
|
||||||
|
CreateExpose(ctx context.Context, req mgm.ExposeRequest) (*mgm.ExposeResponse, error)
|
||||||
|
RenewExpose(ctx context.Context, domain string) error
|
||||||
|
StopExpose(ctx context.Context, domain string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Manager handles expose session lifecycle via the management client.
|
||||||
|
type Manager struct {
|
||||||
|
mgmClient ManagementClient
|
||||||
|
ctx context.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewManager creates a new expose Manager using the given management client.
|
||||||
|
func NewManager(ctx context.Context, mgmClient ManagementClient) *Manager {
|
||||||
|
return &Manager{mgmClient: mgmClient, ctx: ctx}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expose creates a new expose session via the management server.
|
||||||
|
func (m *Manager) Expose(ctx context.Context, req Request) (*Response, error) {
|
||||||
|
log.Infof("exposing service on port %d", req.Port)
|
||||||
|
resp, err := m.mgmClient.CreateExpose(ctx, toClientExposeRequest(req))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("expose session created for %s", resp.Domain)
|
||||||
|
|
||||||
|
return fromClientExposeResponse(resp), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) KeepAlive(ctx context.Context, domain string) error {
|
||||||
|
ticker := time.NewTicker(30 * time.Second)
|
||||||
|
defer ticker.Stop()
|
||||||
|
defer m.stop(domain)
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
log.Infof("context canceled, stopping keep alive for %s", domain)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
case <-ticker.C:
|
||||||
|
if err := m.renew(ctx, domain); err != nil {
|
||||||
|
log.Errorf("renewing expose session for %s: %v", domain, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// renew extends the TTL of an active expose session.
|
||||||
|
func (m *Manager) renew(ctx context.Context, domain string) error {
|
||||||
|
renewCtx, cancel := context.WithTimeout(ctx, renewTimeout)
|
||||||
|
defer cancel()
|
||||||
|
return m.mgmClient.RenewExpose(renewCtx, domain)
|
||||||
|
}
|
||||||
|
|
||||||
|
// stop terminates an active expose session.
|
||||||
|
func (m *Manager) stop(domain string) {
|
||||||
|
stopCtx, cancel := context.WithTimeout(m.ctx, renewTimeout)
|
||||||
|
defer cancel()
|
||||||
|
err := m.mgmClient.StopExpose(stopCtx, domain)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("Failed stopping expose session for %s: %v", domain, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
package expose
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
daemonProto "github.com/netbirdio/netbird/client/proto"
|
||||||
|
mgm "github.com/netbirdio/netbird/shared/management/client"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestManager_Expose_Success(t *testing.T) {
|
||||||
|
mock := &mgm.MockClient{
|
||||||
|
CreateExposeFunc: func(ctx context.Context, req mgm.ExposeRequest) (*mgm.ExposeResponse, error) {
|
||||||
|
return &mgm.ExposeResponse{
|
||||||
|
ServiceName: "my-service",
|
||||||
|
ServiceURL: "https://my-service.example.com",
|
||||||
|
Domain: "my-service.example.com",
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
m := NewManager(context.Background(), mock)
|
||||||
|
result, err := m.Expose(context.Background(), Request{Port: 8080})
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, "my-service", result.ServiceName, "service name should match")
|
||||||
|
assert.Equal(t, "https://my-service.example.com", result.ServiceURL, "service URL should match")
|
||||||
|
assert.Equal(t, "my-service.example.com", result.Domain, "domain should match")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestManager_Expose_Error(t *testing.T) {
|
||||||
|
mock := &mgm.MockClient{
|
||||||
|
CreateExposeFunc: func(ctx context.Context, req mgm.ExposeRequest) (*mgm.ExposeResponse, error) {
|
||||||
|
return nil, errors.New("permission denied")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
m := NewManager(context.Background(), mock)
|
||||||
|
_, err := m.Expose(context.Background(), Request{Port: 8080})
|
||||||
|
require.Error(t, err)
|
||||||
|
assert.Contains(t, err.Error(), "permission denied", "error should propagate")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestManager_Renew_Success(t *testing.T) {
|
||||||
|
mock := &mgm.MockClient{
|
||||||
|
RenewExposeFunc: func(ctx context.Context, domain string) error {
|
||||||
|
assert.Equal(t, "my-service.example.com", domain, "domain should be passed through")
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
m := NewManager(context.Background(), mock)
|
||||||
|
err := m.renew(context.Background(), "my-service.example.com")
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestManager_Renew_Timeout(t *testing.T) {
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
cancel()
|
||||||
|
|
||||||
|
mock := &mgm.MockClient{
|
||||||
|
RenewExposeFunc: func(ctx context.Context, domain string) error {
|
||||||
|
return ctx.Err()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
m := NewManager(ctx, mock)
|
||||||
|
err := m.renew(ctx, "my-service.example.com")
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewRequest(t *testing.T) {
|
||||||
|
req := &daemonProto.ExposeServiceRequest{
|
||||||
|
Port: 8080,
|
||||||
|
Protocol: daemonProto.ExposeProtocol_EXPOSE_HTTPS,
|
||||||
|
Pin: "123456",
|
||||||
|
Password: "secret",
|
||||||
|
UserGroups: []string{"group1", "group2"},
|
||||||
|
Domain: "custom.example.com",
|
||||||
|
NamePrefix: "my-prefix",
|
||||||
|
}
|
||||||
|
|
||||||
|
exposeReq := NewRequest(req)
|
||||||
|
|
||||||
|
assert.Equal(t, uint16(8080), exposeReq.Port, "port should match")
|
||||||
|
assert.Equal(t, int(daemonProto.ExposeProtocol_EXPOSE_HTTPS), exposeReq.Protocol, "protocol should match")
|
||||||
|
assert.Equal(t, "123456", exposeReq.Pin, "pin should match")
|
||||||
|
assert.Equal(t, "secret", exposeReq.Password, "password should match")
|
||||||
|
assert.Equal(t, []string{"group1", "group2"}, exposeReq.UserGroups, "user groups should match")
|
||||||
|
assert.Equal(t, "custom.example.com", exposeReq.Domain, "domain should match")
|
||||||
|
assert.Equal(t, "my-prefix", exposeReq.NamePrefix, "name prefix should match")
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package expose
|
||||||
|
|
||||||
|
import (
|
||||||
|
daemonProto "github.com/netbirdio/netbird/client/proto"
|
||||||
|
mgm "github.com/netbirdio/netbird/shared/management/client"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewRequest converts a daemon ExposeServiceRequest to a management ExposeServiceRequest.
|
||||||
|
func NewRequest(req *daemonProto.ExposeServiceRequest) *Request {
|
||||||
|
return &Request{
|
||||||
|
Port: uint16(req.Port),
|
||||||
|
Protocol: int(req.Protocol),
|
||||||
|
Pin: req.Pin,
|
||||||
|
Password: req.Password,
|
||||||
|
UserGroups: req.UserGroups,
|
||||||
|
Domain: req.Domain,
|
||||||
|
NamePrefix: req.NamePrefix,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func toClientExposeRequest(req Request) mgm.ExposeRequest {
|
||||||
|
return mgm.ExposeRequest{
|
||||||
|
NamePrefix: req.NamePrefix,
|
||||||
|
Domain: req.Domain,
|
||||||
|
Port: req.Port,
|
||||||
|
Protocol: req.Protocol,
|
||||||
|
Pin: req.Pin,
|
||||||
|
Password: req.Password,
|
||||||
|
UserGroups: req.UserGroups,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fromClientExposeResponse(response *mgm.ExposeResponse) *Response {
|
||||||
|
return &Response{
|
||||||
|
ServiceName: response.ServiceName,
|
||||||
|
Domain: response.Domain,
|
||||||
|
ServiceURL: response.ServiceURL,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,51 +22,56 @@ func prepareFd() (int, error) {
|
|||||||
|
|
||||||
func routeCheck(ctx context.Context, fd int, nexthopv4, nexthopv6 systemops.Nexthop) error {
|
func routeCheck(ctx context.Context, fd int, nexthopv4, nexthopv6 systemops.Nexthop) error {
|
||||||
for {
|
for {
|
||||||
select {
|
// Wait until fd is readable or context is cancelled, to avoid a busy-loop
|
||||||
case <-ctx.Done():
|
// when the routing socket returns EAGAIN (e.g. immediately after wakeup).
|
||||||
return ctx.Err()
|
if err := waitReadable(ctx, fd); err != nil {
|
||||||
default:
|
return err
|
||||||
buf := make([]byte, 2048)
|
}
|
||||||
n, err := unix.Read(fd, buf)
|
|
||||||
|
buf := make([]byte, 2048)
|
||||||
|
n, err := unix.Read(fd, buf)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, unix.EAGAIN) || errors.Is(err, unix.EINTR) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if errors.Is(err, unix.EBADF) || errors.Is(err, unix.EINVAL) {
|
||||||
|
return fmt.Errorf("routing socket closed: %w", err)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("read routing socket: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if n < unix.SizeofRtMsghdr {
|
||||||
|
log.Debugf("Network monitor: read from routing socket returned less than expected: %d bytes", n)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
msg := (*unix.RtMsghdr)(unsafe.Pointer(&buf[0]))
|
||||||
|
|
||||||
|
switch msg.Type {
|
||||||
|
// handle route changes
|
||||||
|
case unix.RTM_ADD, syscall.RTM_DELETE:
|
||||||
|
route, err := parseRouteMessage(buf[:n])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !errors.Is(err, unix.EBADF) && !errors.Is(err, unix.EINVAL) {
|
log.Debugf("Network monitor: error parsing routing message: %v", err)
|
||||||
log.Warnf("Network monitor: failed to read from routing socket: %v", err)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if n < unix.SizeofRtMsghdr {
|
|
||||||
log.Debugf("Network monitor: read from routing socket returned less than expected: %d bytes", n)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
msg := (*unix.RtMsghdr)(unsafe.Pointer(&buf[0]))
|
if route.Dst.Bits() != 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
intf := "<nil>"
|
||||||
|
if route.Interface != nil {
|
||||||
|
intf = route.Interface.Name
|
||||||
|
}
|
||||||
switch msg.Type {
|
switch msg.Type {
|
||||||
// handle route changes
|
case unix.RTM_ADD:
|
||||||
case unix.RTM_ADD, syscall.RTM_DELETE:
|
log.Infof("Network monitor: default route changed: via %s, interface %s", route.Gw, intf)
|
||||||
route, err := parseRouteMessage(buf[:n])
|
return nil
|
||||||
if err != nil {
|
case unix.RTM_DELETE:
|
||||||
log.Debugf("Network monitor: error parsing routing message: %v", err)
|
if nexthopv4.Intf != nil && route.Gw.Compare(nexthopv4.IP) == 0 || nexthopv6.Intf != nil && route.Gw.Compare(nexthopv6.IP) == 0 {
|
||||||
continue
|
log.Infof("Network monitor: default route removed: via %s, interface %s", route.Gw, intf)
|
||||||
}
|
|
||||||
|
|
||||||
if route.Dst.Bits() != 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
intf := "<nil>"
|
|
||||||
if route.Interface != nil {
|
|
||||||
intf = route.Interface.Name
|
|
||||||
}
|
|
||||||
switch msg.Type {
|
|
||||||
case unix.RTM_ADD:
|
|
||||||
log.Infof("Network monitor: default route changed: via %s, interface %s", route.Gw, intf)
|
|
||||||
return nil
|
return nil
|
||||||
case unix.RTM_DELETE:
|
|
||||||
if nexthopv4.Intf != nil && route.Gw.Compare(nexthopv4.IP) == 0 || nexthopv6.Intf != nil && route.Gw.Compare(nexthopv6.IP) == 0 {
|
|
||||||
log.Infof("Network monitor: default route removed: via %s, interface %s", route.Gw, intf)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -90,3 +95,33 @@ func parseRouteMessage(buf []byte) (*systemops.Route, error) {
|
|||||||
|
|
||||||
return systemops.MsgToRoute(msg)
|
return systemops.MsgToRoute(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// waitReadable blocks until fd has data to read, or ctx is cancelled.
|
||||||
|
func waitReadable(ctx context.Context, fd int) error {
|
||||||
|
var fdset unix.FdSet
|
||||||
|
if fd < 0 || fd/unix.NFDBITS >= len(fdset.Bits) {
|
||||||
|
return fmt.Errorf("fd %d out of range for FdSet", fd)
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
if err := ctx.Err(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fdset = unix.FdSet{}
|
||||||
|
fdset.Set(fd)
|
||||||
|
// Use a 1-second timeout so we can re-check ctx periodically.
|
||||||
|
tv := unix.Timeval{Sec: 1}
|
||||||
|
n, err := unix.Select(fd+1, &fdset, nil, nil, &tv)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, unix.EINTR) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return fmt.Errorf("select on routing socket: %w", err)
|
||||||
|
}
|
||||||
|
if n > 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// timeout — loop back and re-check ctx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package peer
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"runtime"
|
"runtime"
|
||||||
@@ -25,7 +24,6 @@ import (
|
|||||||
"github.com/netbirdio/netbird/client/internal/stdnet"
|
"github.com/netbirdio/netbird/client/internal/stdnet"
|
||||||
"github.com/netbirdio/netbird/route"
|
"github.com/netbirdio/netbird/route"
|
||||||
relayClient "github.com/netbirdio/netbird/shared/relay/client"
|
relayClient "github.com/netbirdio/netbird/shared/relay/client"
|
||||||
semaphoregroup "github.com/netbirdio/netbird/util/semaphore-group"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type ServiceDependencies struct {
|
type ServiceDependencies struct {
|
||||||
@@ -34,7 +32,6 @@ type ServiceDependencies struct {
|
|||||||
IFaceDiscover stdnet.ExternalIFaceDiscover
|
IFaceDiscover stdnet.ExternalIFaceDiscover
|
||||||
RelayManager *relayClient.Manager
|
RelayManager *relayClient.Manager
|
||||||
SrWatcher *guard.SRWatcher
|
SrWatcher *guard.SRWatcher
|
||||||
Semaphore *semaphoregroup.SemaphoreGroup
|
|
||||||
PeerConnDispatcher *dispatcher.ConnectionDispatcher
|
PeerConnDispatcher *dispatcher.ConnectionDispatcher
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,9 +108,8 @@ type Conn struct {
|
|||||||
wgProxyRelay wgproxy.Proxy
|
wgProxyRelay wgproxy.Proxy
|
||||||
handshaker *Handshaker
|
handshaker *Handshaker
|
||||||
|
|
||||||
guard *guard.Guard
|
guard *guard.Guard
|
||||||
semaphore *semaphoregroup.SemaphoreGroup
|
wg sync.WaitGroup
|
||||||
wg sync.WaitGroup
|
|
||||||
|
|
||||||
// debug purpose
|
// debug purpose
|
||||||
dumpState *stateDump
|
dumpState *stateDump
|
||||||
@@ -139,7 +135,6 @@ func NewConn(config ConnConfig, services ServiceDependencies) (*Conn, error) {
|
|||||||
iFaceDiscover: services.IFaceDiscover,
|
iFaceDiscover: services.IFaceDiscover,
|
||||||
relayManager: services.RelayManager,
|
relayManager: services.RelayManager,
|
||||||
srWatcher: services.SrWatcher,
|
srWatcher: services.SrWatcher,
|
||||||
semaphore: services.Semaphore,
|
|
||||||
statusRelay: worker.NewAtomicStatus(),
|
statusRelay: worker.NewAtomicStatus(),
|
||||||
statusICE: worker.NewAtomicStatus(),
|
statusICE: worker.NewAtomicStatus(),
|
||||||
dumpState: dumpState,
|
dumpState: dumpState,
|
||||||
@@ -154,15 +149,10 @@ func NewConn(config ConnConfig, services ServiceDependencies) (*Conn, error) {
|
|||||||
// It will try to establish a connection using ICE and in parallel with relay. The higher priority connection type will
|
// It will try to establish a connection using ICE and in parallel with relay. The higher priority connection type will
|
||||||
// be used.
|
// be used.
|
||||||
func (conn *Conn) Open(engineCtx context.Context) error {
|
func (conn *Conn) Open(engineCtx context.Context) error {
|
||||||
if err := conn.semaphore.Add(engineCtx); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
conn.mu.Lock()
|
conn.mu.Lock()
|
||||||
defer conn.mu.Unlock()
|
defer conn.mu.Unlock()
|
||||||
|
|
||||||
if conn.opened {
|
if conn.opened {
|
||||||
conn.semaphore.Done()
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,7 +163,6 @@ func (conn *Conn) Open(engineCtx context.Context) error {
|
|||||||
relayIsSupportedLocally := conn.workerRelay.RelayIsSupportedLocally()
|
relayIsSupportedLocally := conn.workerRelay.RelayIsSupportedLocally()
|
||||||
workerICE, err := NewWorkerICE(conn.ctx, conn.Log, conn.config, conn, conn.signaler, conn.iFaceDiscover, conn.statusRecorder, relayIsSupportedLocally)
|
workerICE, err := NewWorkerICE(conn.ctx, conn.Log, conn.config, conn, conn.signaler, conn.iFaceDiscover, conn.statusRecorder, relayIsSupportedLocally)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
conn.semaphore.Done()
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
conn.workerICE = workerICE
|
conn.workerICE = workerICE
|
||||||
@@ -207,10 +196,6 @@ func (conn *Conn) Open(engineCtx context.Context) error {
|
|||||||
conn.wg.Add(1)
|
conn.wg.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
defer conn.wg.Done()
|
defer conn.wg.Done()
|
||||||
|
|
||||||
conn.waitInitialRandomSleepTime(conn.ctx)
|
|
||||||
conn.semaphore.Done()
|
|
||||||
|
|
||||||
conn.guard.Start(conn.ctx, conn.onGuardEvent)
|
conn.guard.Start(conn.ctx, conn.onGuardEvent)
|
||||||
}()
|
}()
|
||||||
conn.opened = true
|
conn.opened = true
|
||||||
@@ -670,19 +655,6 @@ func (conn *Conn) doOnConnected(remoteRosenpassPubKey []byte, remoteRosenpassAdd
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (conn *Conn) waitInitialRandomSleepTime(ctx context.Context) {
|
|
||||||
maxWait := 300
|
|
||||||
duration := time.Duration(rand.Intn(maxWait)) * time.Millisecond
|
|
||||||
|
|
||||||
timeout := time.NewTimer(duration)
|
|
||||||
defer timeout.Stop()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
case <-timeout.C:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (conn *Conn) isRelayed() bool {
|
func (conn *Conn) isRelayed() bool {
|
||||||
switch conn.currentConnPriority {
|
switch conn.currentConnPriority {
|
||||||
case conntype.Relay, conntype.ICETurn:
|
case conntype.Relay, conntype.ICETurn:
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import (
|
|||||||
"github.com/netbirdio/netbird/client/internal/peer/ice"
|
"github.com/netbirdio/netbird/client/internal/peer/ice"
|
||||||
"github.com/netbirdio/netbird/client/internal/stdnet"
|
"github.com/netbirdio/netbird/client/internal/stdnet"
|
||||||
"github.com/netbirdio/netbird/util"
|
"github.com/netbirdio/netbird/util"
|
||||||
semaphoregroup "github.com/netbirdio/netbird/util/semaphore-group"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var testDispatcher = dispatcher.NewConnectionDispatcher()
|
var testDispatcher = dispatcher.NewConnectionDispatcher()
|
||||||
@@ -53,7 +52,6 @@ func TestConn_GetKey(t *testing.T) {
|
|||||||
|
|
||||||
sd := ServiceDependencies{
|
sd := ServiceDependencies{
|
||||||
SrWatcher: swWatcher,
|
SrWatcher: swWatcher,
|
||||||
Semaphore: semaphoregroup.NewSemaphoreGroup(1),
|
|
||||||
PeerConnDispatcher: testDispatcher,
|
PeerConnDispatcher: testDispatcher,
|
||||||
}
|
}
|
||||||
conn, err := NewConn(connConf, sd)
|
conn, err := NewConn(connConf, sd)
|
||||||
@@ -71,7 +69,6 @@ func TestConn_OnRemoteOffer(t *testing.T) {
|
|||||||
sd := ServiceDependencies{
|
sd := ServiceDependencies{
|
||||||
StatusRecorder: NewRecorder("https://mgm"),
|
StatusRecorder: NewRecorder("https://mgm"),
|
||||||
SrWatcher: swWatcher,
|
SrWatcher: swWatcher,
|
||||||
Semaphore: semaphoregroup.NewSemaphoreGroup(1),
|
|
||||||
PeerConnDispatcher: testDispatcher,
|
PeerConnDispatcher: testDispatcher,
|
||||||
}
|
}
|
||||||
conn, err := NewConn(connConf, sd)
|
conn, err := NewConn(connConf, sd)
|
||||||
@@ -110,7 +107,6 @@ func TestConn_OnRemoteAnswer(t *testing.T) {
|
|||||||
sd := ServiceDependencies{
|
sd := ServiceDependencies{
|
||||||
StatusRecorder: NewRecorder("https://mgm"),
|
StatusRecorder: NewRecorder("https://mgm"),
|
||||||
SrWatcher: swWatcher,
|
SrWatcher: swWatcher,
|
||||||
Semaphore: semaphoregroup.NewSemaphoreGroup(1),
|
|
||||||
PeerConnDispatcher: testDispatcher,
|
PeerConnDispatcher: testDispatcher,
|
||||||
}
|
}
|
||||||
conn, err := NewConn(connConf, sd)
|
conn, err := NewConn(connConf, sd)
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/client/internal"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Agent interface {
|
||||||
|
Up(ctx context.Context) error
|
||||||
|
Down(ctx context.Context) error
|
||||||
|
Status() (internal.StatusType, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type SleepHandler struct {
|
||||||
|
agent Agent
|
||||||
|
|
||||||
|
mu sync.Mutex
|
||||||
|
// sleepTriggeredDown indicates whether the sleep handler triggered the last client down, to avoid unnecessary up on wake
|
||||||
|
sleepTriggeredDown bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(agent Agent) *SleepHandler {
|
||||||
|
return &SleepHandler{
|
||||||
|
agent: agent,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SleepHandler) HandleWakeUp(ctx context.Context) error {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
|
||||||
|
if !s.sleepTriggeredDown {
|
||||||
|
log.Info("skipping up because wasn't sleep down")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// avoid other wakeup runs if sleep didn't make the computer sleep
|
||||||
|
s.sleepTriggeredDown = false
|
||||||
|
|
||||||
|
log.Info("running up after wake up")
|
||||||
|
err := s.agent.Up(ctx)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("running up failed: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("running up command executed successfully")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SleepHandler) HandleSleep(ctx context.Context) error {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
|
||||||
|
status, err := s.agent.Status()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if status != internal.StatusConnecting && status != internal.StatusConnected {
|
||||||
|
log.Infof("skipping setting the agent down because status is %s", status)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("running down after system started sleeping")
|
||||||
|
|
||||||
|
if err = s.agent.Down(ctx); err != nil {
|
||||||
|
log.Errorf("running down failed: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.sleepTriggeredDown = true
|
||||||
|
|
||||||
|
log.Info("running down executed successfully")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/client/internal"
|
||||||
|
)
|
||||||
|
|
||||||
|
type mockAgent struct {
|
||||||
|
upErr error
|
||||||
|
downErr error
|
||||||
|
statusErr error
|
||||||
|
status internal.StatusType
|
||||||
|
upCalls int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockAgent) Up(_ context.Context) error {
|
||||||
|
m.upCalls++
|
||||||
|
return m.upErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockAgent) Down(_ context.Context) error {
|
||||||
|
return m.downErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockAgent) Status() (internal.StatusType, error) {
|
||||||
|
return m.status, m.statusErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func newHandler(status internal.StatusType) (*SleepHandler, *mockAgent) {
|
||||||
|
agent := &mockAgent{status: status}
|
||||||
|
return New(agent), agent
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleWakeUp_SkipsWhenFlagFalse(t *testing.T) {
|
||||||
|
h, agent := newHandler(internal.StatusIdle)
|
||||||
|
|
||||||
|
err := h.HandleWakeUp(context.Background())
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, 0, agent.upCalls, "Up should not be called when flag is false")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleWakeUp_ResetsFlagBeforeUp(t *testing.T) {
|
||||||
|
h, _ := newHandler(internal.StatusIdle)
|
||||||
|
h.sleepTriggeredDown = true
|
||||||
|
|
||||||
|
// Even if Up fails, flag should be reset
|
||||||
|
_ = h.HandleWakeUp(context.Background())
|
||||||
|
|
||||||
|
assert.False(t, h.sleepTriggeredDown, "flag must be reset before calling Up")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleWakeUp_CallsUpWhenFlagSet(t *testing.T) {
|
||||||
|
h, agent := newHandler(internal.StatusIdle)
|
||||||
|
h.sleepTriggeredDown = true
|
||||||
|
|
||||||
|
err := h.HandleWakeUp(context.Background())
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, 1, agent.upCalls)
|
||||||
|
assert.False(t, h.sleepTriggeredDown)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleWakeUp_ReturnsErrorFromUp(t *testing.T) {
|
||||||
|
h, agent := newHandler(internal.StatusIdle)
|
||||||
|
h.sleepTriggeredDown = true
|
||||||
|
agent.upErr = errors.New("up failed")
|
||||||
|
|
||||||
|
err := h.HandleWakeUp(context.Background())
|
||||||
|
|
||||||
|
assert.ErrorIs(t, err, agent.upErr)
|
||||||
|
assert.False(t, h.sleepTriggeredDown, "flag should still be reset even when Up fails")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleWakeUp_SecondCallIsNoOp(t *testing.T) {
|
||||||
|
h, agent := newHandler(internal.StatusIdle)
|
||||||
|
h.sleepTriggeredDown = true
|
||||||
|
|
||||||
|
_ = h.HandleWakeUp(context.Background())
|
||||||
|
err := h.HandleWakeUp(context.Background())
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, 1, agent.upCalls, "second wakeup should be no-op")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleSleep_SkipsForNonActiveStates(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
status internal.StatusType
|
||||||
|
}{
|
||||||
|
{"Idle", internal.StatusIdle},
|
||||||
|
{"NeedsLogin", internal.StatusNeedsLogin},
|
||||||
|
{"LoginFailed", internal.StatusLoginFailed},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
h, _ := newHandler(tt.status)
|
||||||
|
|
||||||
|
err := h.HandleSleep(context.Background())
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.False(t, h.sleepTriggeredDown)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleSleep_ProceedsForActiveStates(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
status internal.StatusType
|
||||||
|
}{
|
||||||
|
{"Connecting", internal.StatusConnecting},
|
||||||
|
{"Connected", internal.StatusConnected},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
h, _ := newHandler(tt.status)
|
||||||
|
|
||||||
|
err := h.HandleSleep(context.Background())
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.True(t, h.sleepTriggeredDown)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleSleep_ReturnsErrorFromStatus(t *testing.T) {
|
||||||
|
agent := &mockAgent{statusErr: errors.New("status error")}
|
||||||
|
h := New(agent)
|
||||||
|
|
||||||
|
err := h.HandleSleep(context.Background())
|
||||||
|
|
||||||
|
assert.ErrorIs(t, err, agent.statusErr)
|
||||||
|
assert.False(t, h.sleepTriggeredDown)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleSleep_ReturnsErrorFromDown(t *testing.T) {
|
||||||
|
agent := &mockAgent{status: internal.StatusConnected, downErr: errors.New("down failed")}
|
||||||
|
h := New(agent)
|
||||||
|
|
||||||
|
err := h.HandleSleep(context.Background())
|
||||||
|
|
||||||
|
assert.ErrorIs(t, err, agent.downErr)
|
||||||
|
assert.False(t, h.sleepTriggeredDown, "flag should not be set when Down fails")
|
||||||
|
}
|
||||||
+523
-216
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.36.6
|
// protoc-gen-go v1.36.6
|
||||||
// protoc v6.32.1
|
// protoc v6.33.3
|
||||||
// source: daemon.proto
|
// source: daemon.proto
|
||||||
|
|
||||||
package proto
|
package proto
|
||||||
@@ -88,6 +88,58 @@ func (LogLevel) EnumDescriptor() ([]byte, []int) {
|
|||||||
return file_daemon_proto_rawDescGZIP(), []int{0}
|
return file_daemon_proto_rawDescGZIP(), []int{0}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ExposeProtocol int32
|
||||||
|
|
||||||
|
const (
|
||||||
|
ExposeProtocol_EXPOSE_HTTP ExposeProtocol = 0
|
||||||
|
ExposeProtocol_EXPOSE_HTTPS ExposeProtocol = 1
|
||||||
|
ExposeProtocol_EXPOSE_TCP ExposeProtocol = 2
|
||||||
|
ExposeProtocol_EXPOSE_UDP ExposeProtocol = 3
|
||||||
|
)
|
||||||
|
|
||||||
|
// Enum value maps for ExposeProtocol.
|
||||||
|
var (
|
||||||
|
ExposeProtocol_name = map[int32]string{
|
||||||
|
0: "EXPOSE_HTTP",
|
||||||
|
1: "EXPOSE_HTTPS",
|
||||||
|
2: "EXPOSE_TCP",
|
||||||
|
3: "EXPOSE_UDP",
|
||||||
|
}
|
||||||
|
ExposeProtocol_value = map[string]int32{
|
||||||
|
"EXPOSE_HTTP": 0,
|
||||||
|
"EXPOSE_HTTPS": 1,
|
||||||
|
"EXPOSE_TCP": 2,
|
||||||
|
"EXPOSE_UDP": 3,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (x ExposeProtocol) Enum() *ExposeProtocol {
|
||||||
|
p := new(ExposeProtocol)
|
||||||
|
*p = x
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x ExposeProtocol) String() string {
|
||||||
|
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ExposeProtocol) Descriptor() protoreflect.EnumDescriptor {
|
||||||
|
return file_daemon_proto_enumTypes[1].Descriptor()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ExposeProtocol) Type() protoreflect.EnumType {
|
||||||
|
return &file_daemon_proto_enumTypes[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x ExposeProtocol) Number() protoreflect.EnumNumber {
|
||||||
|
return protoreflect.EnumNumber(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use ExposeProtocol.Descriptor instead.
|
||||||
|
func (ExposeProtocol) EnumDescriptor() ([]byte, []int) {
|
||||||
|
return file_daemon_proto_rawDescGZIP(), []int{1}
|
||||||
|
}
|
||||||
|
|
||||||
// avoid collision with loglevel enum
|
// avoid collision with loglevel enum
|
||||||
type OSLifecycleRequest_CycleType int32
|
type OSLifecycleRequest_CycleType int32
|
||||||
|
|
||||||
@@ -122,11 +174,11 @@ func (x OSLifecycleRequest_CycleType) String() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (OSLifecycleRequest_CycleType) Descriptor() protoreflect.EnumDescriptor {
|
func (OSLifecycleRequest_CycleType) Descriptor() protoreflect.EnumDescriptor {
|
||||||
return file_daemon_proto_enumTypes[1].Descriptor()
|
return file_daemon_proto_enumTypes[2].Descriptor()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (OSLifecycleRequest_CycleType) Type() protoreflect.EnumType {
|
func (OSLifecycleRequest_CycleType) Type() protoreflect.EnumType {
|
||||||
return &file_daemon_proto_enumTypes[1]
|
return &file_daemon_proto_enumTypes[2]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x OSLifecycleRequest_CycleType) Number() protoreflect.EnumNumber {
|
func (x OSLifecycleRequest_CycleType) Number() protoreflect.EnumNumber {
|
||||||
@@ -174,11 +226,11 @@ func (x SystemEvent_Severity) String() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (SystemEvent_Severity) Descriptor() protoreflect.EnumDescriptor {
|
func (SystemEvent_Severity) Descriptor() protoreflect.EnumDescriptor {
|
||||||
return file_daemon_proto_enumTypes[2].Descriptor()
|
return file_daemon_proto_enumTypes[3].Descriptor()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (SystemEvent_Severity) Type() protoreflect.EnumType {
|
func (SystemEvent_Severity) Type() protoreflect.EnumType {
|
||||||
return &file_daemon_proto_enumTypes[2]
|
return &file_daemon_proto_enumTypes[3]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x SystemEvent_Severity) Number() protoreflect.EnumNumber {
|
func (x SystemEvent_Severity) Number() protoreflect.EnumNumber {
|
||||||
@@ -229,11 +281,11 @@ func (x SystemEvent_Category) String() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (SystemEvent_Category) Descriptor() protoreflect.EnumDescriptor {
|
func (SystemEvent_Category) Descriptor() protoreflect.EnumDescriptor {
|
||||||
return file_daemon_proto_enumTypes[3].Descriptor()
|
return file_daemon_proto_enumTypes[4].Descriptor()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (SystemEvent_Category) Type() protoreflect.EnumType {
|
func (SystemEvent_Category) Type() protoreflect.EnumType {
|
||||||
return &file_daemon_proto_enumTypes[3]
|
return &file_daemon_proto_enumTypes[4]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x SystemEvent_Category) Number() protoreflect.EnumNumber {
|
func (x SystemEvent_Category) Number() protoreflect.EnumNumber {
|
||||||
@@ -5600,6 +5652,224 @@ func (x *InstallerResultResponse) GetErrorMsg() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ExposeServiceRequest struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
Port uint32 `protobuf:"varint,1,opt,name=port,proto3" json:"port,omitempty"`
|
||||||
|
Protocol ExposeProtocol `protobuf:"varint,2,opt,name=protocol,proto3,enum=daemon.ExposeProtocol" json:"protocol,omitempty"`
|
||||||
|
Pin string `protobuf:"bytes,3,opt,name=pin,proto3" json:"pin,omitempty"`
|
||||||
|
Password string `protobuf:"bytes,4,opt,name=password,proto3" json:"password,omitempty"`
|
||||||
|
UserGroups []string `protobuf:"bytes,5,rep,name=user_groups,json=userGroups,proto3" json:"user_groups,omitempty"`
|
||||||
|
Domain string `protobuf:"bytes,6,opt,name=domain,proto3" json:"domain,omitempty"`
|
||||||
|
NamePrefix string `protobuf:"bytes,7,opt,name=name_prefix,json=namePrefix,proto3" json:"name_prefix,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ExposeServiceRequest) Reset() {
|
||||||
|
*x = ExposeServiceRequest{}
|
||||||
|
mi := &file_daemon_proto_msgTypes[85]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ExposeServiceRequest) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*ExposeServiceRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *ExposeServiceRequest) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_daemon_proto_msgTypes[85]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use ExposeServiceRequest.ProtoReflect.Descriptor instead.
|
||||||
|
func (*ExposeServiceRequest) Descriptor() ([]byte, []int) {
|
||||||
|
return file_daemon_proto_rawDescGZIP(), []int{85}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ExposeServiceRequest) GetPort() uint32 {
|
||||||
|
if x != nil {
|
||||||
|
return x.Port
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ExposeServiceRequest) GetProtocol() ExposeProtocol {
|
||||||
|
if x != nil {
|
||||||
|
return x.Protocol
|
||||||
|
}
|
||||||
|
return ExposeProtocol_EXPOSE_HTTP
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ExposeServiceRequest) GetPin() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Pin
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ExposeServiceRequest) GetPassword() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Password
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ExposeServiceRequest) GetUserGroups() []string {
|
||||||
|
if x != nil {
|
||||||
|
return x.UserGroups
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ExposeServiceRequest) GetDomain() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Domain
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ExposeServiceRequest) GetNamePrefix() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.NamePrefix
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExposeServiceEvent struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
// Types that are valid to be assigned to Event:
|
||||||
|
//
|
||||||
|
// *ExposeServiceEvent_Ready
|
||||||
|
Event isExposeServiceEvent_Event `protobuf_oneof:"event"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ExposeServiceEvent) Reset() {
|
||||||
|
*x = ExposeServiceEvent{}
|
||||||
|
mi := &file_daemon_proto_msgTypes[86]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ExposeServiceEvent) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*ExposeServiceEvent) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *ExposeServiceEvent) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_daemon_proto_msgTypes[86]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use ExposeServiceEvent.ProtoReflect.Descriptor instead.
|
||||||
|
func (*ExposeServiceEvent) Descriptor() ([]byte, []int) {
|
||||||
|
return file_daemon_proto_rawDescGZIP(), []int{86}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ExposeServiceEvent) GetEvent() isExposeServiceEvent_Event {
|
||||||
|
if x != nil {
|
||||||
|
return x.Event
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ExposeServiceEvent) GetReady() *ExposeServiceReady {
|
||||||
|
if x != nil {
|
||||||
|
if x, ok := x.Event.(*ExposeServiceEvent_Ready); ok {
|
||||||
|
return x.Ready
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type isExposeServiceEvent_Event interface {
|
||||||
|
isExposeServiceEvent_Event()
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExposeServiceEvent_Ready struct {
|
||||||
|
Ready *ExposeServiceReady `protobuf:"bytes,1,opt,name=ready,proto3,oneof"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*ExposeServiceEvent_Ready) isExposeServiceEvent_Event() {}
|
||||||
|
|
||||||
|
type ExposeServiceReady struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
ServiceName string `protobuf:"bytes,1,opt,name=service_name,json=serviceName,proto3" json:"service_name,omitempty"`
|
||||||
|
ServiceUrl string `protobuf:"bytes,2,opt,name=service_url,json=serviceUrl,proto3" json:"service_url,omitempty"`
|
||||||
|
Domain string `protobuf:"bytes,3,opt,name=domain,proto3" json:"domain,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ExposeServiceReady) Reset() {
|
||||||
|
*x = ExposeServiceReady{}
|
||||||
|
mi := &file_daemon_proto_msgTypes[87]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ExposeServiceReady) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*ExposeServiceReady) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *ExposeServiceReady) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_daemon_proto_msgTypes[87]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use ExposeServiceReady.ProtoReflect.Descriptor instead.
|
||||||
|
func (*ExposeServiceReady) Descriptor() ([]byte, []int) {
|
||||||
|
return file_daemon_proto_rawDescGZIP(), []int{87}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ExposeServiceReady) GetServiceName() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.ServiceName
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ExposeServiceReady) GetServiceUrl() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.ServiceUrl
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ExposeServiceReady) GetDomain() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Domain
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
type PortInfo_Range struct {
|
type PortInfo_Range struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
Start uint32 `protobuf:"varint,1,opt,name=start,proto3" json:"start,omitempty"`
|
Start uint32 `protobuf:"varint,1,opt,name=start,proto3" json:"start,omitempty"`
|
||||||
@@ -5610,7 +5880,7 @@ type PortInfo_Range struct {
|
|||||||
|
|
||||||
func (x *PortInfo_Range) Reset() {
|
func (x *PortInfo_Range) Reset() {
|
||||||
*x = PortInfo_Range{}
|
*x = PortInfo_Range{}
|
||||||
mi := &file_daemon_proto_msgTypes[86]
|
mi := &file_daemon_proto_msgTypes[89]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@@ -5622,7 +5892,7 @@ func (x *PortInfo_Range) String() string {
|
|||||||
func (*PortInfo_Range) ProtoMessage() {}
|
func (*PortInfo_Range) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *PortInfo_Range) ProtoReflect() protoreflect.Message {
|
func (x *PortInfo_Range) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_daemon_proto_msgTypes[86]
|
mi := &file_daemon_proto_msgTypes[89]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@@ -6149,7 +6419,25 @@ const file_daemon_proto_rawDesc = "" +
|
|||||||
"\x16InstallerResultRequest\"O\n" +
|
"\x16InstallerResultRequest\"O\n" +
|
||||||
"\x17InstallerResultResponse\x12\x18\n" +
|
"\x17InstallerResultResponse\x12\x18\n" +
|
||||||
"\asuccess\x18\x01 \x01(\bR\asuccess\x12\x1a\n" +
|
"\asuccess\x18\x01 \x01(\bR\asuccess\x12\x1a\n" +
|
||||||
"\berrorMsg\x18\x02 \x01(\tR\berrorMsg*b\n" +
|
"\berrorMsg\x18\x02 \x01(\tR\berrorMsg\"\xe6\x01\n" +
|
||||||
|
"\x14ExposeServiceRequest\x12\x12\n" +
|
||||||
|
"\x04port\x18\x01 \x01(\rR\x04port\x122\n" +
|
||||||
|
"\bprotocol\x18\x02 \x01(\x0e2\x16.daemon.ExposeProtocolR\bprotocol\x12\x10\n" +
|
||||||
|
"\x03pin\x18\x03 \x01(\tR\x03pin\x12\x1a\n" +
|
||||||
|
"\bpassword\x18\x04 \x01(\tR\bpassword\x12\x1f\n" +
|
||||||
|
"\vuser_groups\x18\x05 \x03(\tR\n" +
|
||||||
|
"userGroups\x12\x16\n" +
|
||||||
|
"\x06domain\x18\x06 \x01(\tR\x06domain\x12\x1f\n" +
|
||||||
|
"\vname_prefix\x18\a \x01(\tR\n" +
|
||||||
|
"namePrefix\"Q\n" +
|
||||||
|
"\x12ExposeServiceEvent\x122\n" +
|
||||||
|
"\x05ready\x18\x01 \x01(\v2\x1a.daemon.ExposeServiceReadyH\x00R\x05readyB\a\n" +
|
||||||
|
"\x05event\"p\n" +
|
||||||
|
"\x12ExposeServiceReady\x12!\n" +
|
||||||
|
"\fservice_name\x18\x01 \x01(\tR\vserviceName\x12\x1f\n" +
|
||||||
|
"\vservice_url\x18\x02 \x01(\tR\n" +
|
||||||
|
"serviceUrl\x12\x16\n" +
|
||||||
|
"\x06domain\x18\x03 \x01(\tR\x06domain*b\n" +
|
||||||
"\bLogLevel\x12\v\n" +
|
"\bLogLevel\x12\v\n" +
|
||||||
"\aUNKNOWN\x10\x00\x12\t\n" +
|
"\aUNKNOWN\x10\x00\x12\t\n" +
|
||||||
"\x05PANIC\x10\x01\x12\t\n" +
|
"\x05PANIC\x10\x01\x12\t\n" +
|
||||||
@@ -6158,7 +6446,14 @@ const file_daemon_proto_rawDesc = "" +
|
|||||||
"\x04WARN\x10\x04\x12\b\n" +
|
"\x04WARN\x10\x04\x12\b\n" +
|
||||||
"\x04INFO\x10\x05\x12\t\n" +
|
"\x04INFO\x10\x05\x12\t\n" +
|
||||||
"\x05DEBUG\x10\x06\x12\t\n" +
|
"\x05DEBUG\x10\x06\x12\t\n" +
|
||||||
"\x05TRACE\x10\a2\xdd\x14\n" +
|
"\x05TRACE\x10\a*S\n" +
|
||||||
|
"\x0eExposeProtocol\x12\x0f\n" +
|
||||||
|
"\vEXPOSE_HTTP\x10\x00\x12\x10\n" +
|
||||||
|
"\fEXPOSE_HTTPS\x10\x01\x12\x0e\n" +
|
||||||
|
"\n" +
|
||||||
|
"EXPOSE_TCP\x10\x02\x12\x0e\n" +
|
||||||
|
"\n" +
|
||||||
|
"EXPOSE_UDP\x10\x032\xac\x15\n" +
|
||||||
"\rDaemonService\x126\n" +
|
"\rDaemonService\x126\n" +
|
||||||
"\x05Login\x12\x14.daemon.LoginRequest\x1a\x15.daemon.LoginResponse\"\x00\x12K\n" +
|
"\x05Login\x12\x14.daemon.LoginRequest\x1a\x15.daemon.LoginResponse\"\x00\x12K\n" +
|
||||||
"\fWaitSSOLogin\x12\x1b.daemon.WaitSSOLoginRequest\x1a\x1c.daemon.WaitSSOLoginResponse\"\x00\x12-\n" +
|
"\fWaitSSOLogin\x12\x1b.daemon.WaitSSOLoginRequest\x1a\x1c.daemon.WaitSSOLoginResponse\"\x00\x12-\n" +
|
||||||
@@ -6197,7 +6492,8 @@ const file_daemon_proto_rawDesc = "" +
|
|||||||
"\x0fStartCPUProfile\x12\x1e.daemon.StartCPUProfileRequest\x1a\x1f.daemon.StartCPUProfileResponse\"\x00\x12Q\n" +
|
"\x0fStartCPUProfile\x12\x1e.daemon.StartCPUProfileRequest\x1a\x1f.daemon.StartCPUProfileResponse\"\x00\x12Q\n" +
|
||||||
"\x0eStopCPUProfile\x12\x1d.daemon.StopCPUProfileRequest\x1a\x1e.daemon.StopCPUProfileResponse\"\x00\x12N\n" +
|
"\x0eStopCPUProfile\x12\x1d.daemon.StopCPUProfileRequest\x1a\x1e.daemon.StopCPUProfileResponse\"\x00\x12N\n" +
|
||||||
"\x11NotifyOSLifecycle\x12\x1a.daemon.OSLifecycleRequest\x1a\x1b.daemon.OSLifecycleResponse\"\x00\x12W\n" +
|
"\x11NotifyOSLifecycle\x12\x1a.daemon.OSLifecycleRequest\x1a\x1b.daemon.OSLifecycleResponse\"\x00\x12W\n" +
|
||||||
"\x12GetInstallerResult\x12\x1e.daemon.InstallerResultRequest\x1a\x1f.daemon.InstallerResultResponse\"\x00B\bZ\x06/protob\x06proto3"
|
"\x12GetInstallerResult\x12\x1e.daemon.InstallerResultRequest\x1a\x1f.daemon.InstallerResultResponse\"\x00\x12M\n" +
|
||||||
|
"\rExposeService\x12\x1c.daemon.ExposeServiceRequest\x1a\x1a.daemon.ExposeServiceEvent\"\x000\x01B\bZ\x06/protob\x06proto3"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
file_daemon_proto_rawDescOnce sync.Once
|
file_daemon_proto_rawDescOnce sync.Once
|
||||||
@@ -6211,214 +6507,222 @@ func file_daemon_proto_rawDescGZIP() []byte {
|
|||||||
return file_daemon_proto_rawDescData
|
return file_daemon_proto_rawDescData
|
||||||
}
|
}
|
||||||
|
|
||||||
var file_daemon_proto_enumTypes = make([]protoimpl.EnumInfo, 4)
|
var file_daemon_proto_enumTypes = make([]protoimpl.EnumInfo, 5)
|
||||||
var file_daemon_proto_msgTypes = make([]protoimpl.MessageInfo, 88)
|
var file_daemon_proto_msgTypes = make([]protoimpl.MessageInfo, 91)
|
||||||
var file_daemon_proto_goTypes = []any{
|
var file_daemon_proto_goTypes = []any{
|
||||||
(LogLevel)(0), // 0: daemon.LogLevel
|
(LogLevel)(0), // 0: daemon.LogLevel
|
||||||
(OSLifecycleRequest_CycleType)(0), // 1: daemon.OSLifecycleRequest.CycleType
|
(ExposeProtocol)(0), // 1: daemon.ExposeProtocol
|
||||||
(SystemEvent_Severity)(0), // 2: daemon.SystemEvent.Severity
|
(OSLifecycleRequest_CycleType)(0), // 2: daemon.OSLifecycleRequest.CycleType
|
||||||
(SystemEvent_Category)(0), // 3: daemon.SystemEvent.Category
|
(SystemEvent_Severity)(0), // 3: daemon.SystemEvent.Severity
|
||||||
(*EmptyRequest)(nil), // 4: daemon.EmptyRequest
|
(SystemEvent_Category)(0), // 4: daemon.SystemEvent.Category
|
||||||
(*OSLifecycleRequest)(nil), // 5: daemon.OSLifecycleRequest
|
(*EmptyRequest)(nil), // 5: daemon.EmptyRequest
|
||||||
(*OSLifecycleResponse)(nil), // 6: daemon.OSLifecycleResponse
|
(*OSLifecycleRequest)(nil), // 6: daemon.OSLifecycleRequest
|
||||||
(*LoginRequest)(nil), // 7: daemon.LoginRequest
|
(*OSLifecycleResponse)(nil), // 7: daemon.OSLifecycleResponse
|
||||||
(*LoginResponse)(nil), // 8: daemon.LoginResponse
|
(*LoginRequest)(nil), // 8: daemon.LoginRequest
|
||||||
(*WaitSSOLoginRequest)(nil), // 9: daemon.WaitSSOLoginRequest
|
(*LoginResponse)(nil), // 9: daemon.LoginResponse
|
||||||
(*WaitSSOLoginResponse)(nil), // 10: daemon.WaitSSOLoginResponse
|
(*WaitSSOLoginRequest)(nil), // 10: daemon.WaitSSOLoginRequest
|
||||||
(*UpRequest)(nil), // 11: daemon.UpRequest
|
(*WaitSSOLoginResponse)(nil), // 11: daemon.WaitSSOLoginResponse
|
||||||
(*UpResponse)(nil), // 12: daemon.UpResponse
|
(*UpRequest)(nil), // 12: daemon.UpRequest
|
||||||
(*StatusRequest)(nil), // 13: daemon.StatusRequest
|
(*UpResponse)(nil), // 13: daemon.UpResponse
|
||||||
(*StatusResponse)(nil), // 14: daemon.StatusResponse
|
(*StatusRequest)(nil), // 14: daemon.StatusRequest
|
||||||
(*DownRequest)(nil), // 15: daemon.DownRequest
|
(*StatusResponse)(nil), // 15: daemon.StatusResponse
|
||||||
(*DownResponse)(nil), // 16: daemon.DownResponse
|
(*DownRequest)(nil), // 16: daemon.DownRequest
|
||||||
(*GetConfigRequest)(nil), // 17: daemon.GetConfigRequest
|
(*DownResponse)(nil), // 17: daemon.DownResponse
|
||||||
(*GetConfigResponse)(nil), // 18: daemon.GetConfigResponse
|
(*GetConfigRequest)(nil), // 18: daemon.GetConfigRequest
|
||||||
(*PeerState)(nil), // 19: daemon.PeerState
|
(*GetConfigResponse)(nil), // 19: daemon.GetConfigResponse
|
||||||
(*LocalPeerState)(nil), // 20: daemon.LocalPeerState
|
(*PeerState)(nil), // 20: daemon.PeerState
|
||||||
(*SignalState)(nil), // 21: daemon.SignalState
|
(*LocalPeerState)(nil), // 21: daemon.LocalPeerState
|
||||||
(*ManagementState)(nil), // 22: daemon.ManagementState
|
(*SignalState)(nil), // 22: daemon.SignalState
|
||||||
(*RelayState)(nil), // 23: daemon.RelayState
|
(*ManagementState)(nil), // 23: daemon.ManagementState
|
||||||
(*NSGroupState)(nil), // 24: daemon.NSGroupState
|
(*RelayState)(nil), // 24: daemon.RelayState
|
||||||
(*SSHSessionInfo)(nil), // 25: daemon.SSHSessionInfo
|
(*NSGroupState)(nil), // 25: daemon.NSGroupState
|
||||||
(*SSHServerState)(nil), // 26: daemon.SSHServerState
|
(*SSHSessionInfo)(nil), // 26: daemon.SSHSessionInfo
|
||||||
(*FullStatus)(nil), // 27: daemon.FullStatus
|
(*SSHServerState)(nil), // 27: daemon.SSHServerState
|
||||||
(*ListNetworksRequest)(nil), // 28: daemon.ListNetworksRequest
|
(*FullStatus)(nil), // 28: daemon.FullStatus
|
||||||
(*ListNetworksResponse)(nil), // 29: daemon.ListNetworksResponse
|
(*ListNetworksRequest)(nil), // 29: daemon.ListNetworksRequest
|
||||||
(*SelectNetworksRequest)(nil), // 30: daemon.SelectNetworksRequest
|
(*ListNetworksResponse)(nil), // 30: daemon.ListNetworksResponse
|
||||||
(*SelectNetworksResponse)(nil), // 31: daemon.SelectNetworksResponse
|
(*SelectNetworksRequest)(nil), // 31: daemon.SelectNetworksRequest
|
||||||
(*IPList)(nil), // 32: daemon.IPList
|
(*SelectNetworksResponse)(nil), // 32: daemon.SelectNetworksResponse
|
||||||
(*Network)(nil), // 33: daemon.Network
|
(*IPList)(nil), // 33: daemon.IPList
|
||||||
(*PortInfo)(nil), // 34: daemon.PortInfo
|
(*Network)(nil), // 34: daemon.Network
|
||||||
(*ForwardingRule)(nil), // 35: daemon.ForwardingRule
|
(*PortInfo)(nil), // 35: daemon.PortInfo
|
||||||
(*ForwardingRulesResponse)(nil), // 36: daemon.ForwardingRulesResponse
|
(*ForwardingRule)(nil), // 36: daemon.ForwardingRule
|
||||||
(*DebugBundleRequest)(nil), // 37: daemon.DebugBundleRequest
|
(*ForwardingRulesResponse)(nil), // 37: daemon.ForwardingRulesResponse
|
||||||
(*DebugBundleResponse)(nil), // 38: daemon.DebugBundleResponse
|
(*DebugBundleRequest)(nil), // 38: daemon.DebugBundleRequest
|
||||||
(*GetLogLevelRequest)(nil), // 39: daemon.GetLogLevelRequest
|
(*DebugBundleResponse)(nil), // 39: daemon.DebugBundleResponse
|
||||||
(*GetLogLevelResponse)(nil), // 40: daemon.GetLogLevelResponse
|
(*GetLogLevelRequest)(nil), // 40: daemon.GetLogLevelRequest
|
||||||
(*SetLogLevelRequest)(nil), // 41: daemon.SetLogLevelRequest
|
(*GetLogLevelResponse)(nil), // 41: daemon.GetLogLevelResponse
|
||||||
(*SetLogLevelResponse)(nil), // 42: daemon.SetLogLevelResponse
|
(*SetLogLevelRequest)(nil), // 42: daemon.SetLogLevelRequest
|
||||||
(*State)(nil), // 43: daemon.State
|
(*SetLogLevelResponse)(nil), // 43: daemon.SetLogLevelResponse
|
||||||
(*ListStatesRequest)(nil), // 44: daemon.ListStatesRequest
|
(*State)(nil), // 44: daemon.State
|
||||||
(*ListStatesResponse)(nil), // 45: daemon.ListStatesResponse
|
(*ListStatesRequest)(nil), // 45: daemon.ListStatesRequest
|
||||||
(*CleanStateRequest)(nil), // 46: daemon.CleanStateRequest
|
(*ListStatesResponse)(nil), // 46: daemon.ListStatesResponse
|
||||||
(*CleanStateResponse)(nil), // 47: daemon.CleanStateResponse
|
(*CleanStateRequest)(nil), // 47: daemon.CleanStateRequest
|
||||||
(*DeleteStateRequest)(nil), // 48: daemon.DeleteStateRequest
|
(*CleanStateResponse)(nil), // 48: daemon.CleanStateResponse
|
||||||
(*DeleteStateResponse)(nil), // 49: daemon.DeleteStateResponse
|
(*DeleteStateRequest)(nil), // 49: daemon.DeleteStateRequest
|
||||||
(*SetSyncResponsePersistenceRequest)(nil), // 50: daemon.SetSyncResponsePersistenceRequest
|
(*DeleteStateResponse)(nil), // 50: daemon.DeleteStateResponse
|
||||||
(*SetSyncResponsePersistenceResponse)(nil), // 51: daemon.SetSyncResponsePersistenceResponse
|
(*SetSyncResponsePersistenceRequest)(nil), // 51: daemon.SetSyncResponsePersistenceRequest
|
||||||
(*TCPFlags)(nil), // 52: daemon.TCPFlags
|
(*SetSyncResponsePersistenceResponse)(nil), // 52: daemon.SetSyncResponsePersistenceResponse
|
||||||
(*TracePacketRequest)(nil), // 53: daemon.TracePacketRequest
|
(*TCPFlags)(nil), // 53: daemon.TCPFlags
|
||||||
(*TraceStage)(nil), // 54: daemon.TraceStage
|
(*TracePacketRequest)(nil), // 54: daemon.TracePacketRequest
|
||||||
(*TracePacketResponse)(nil), // 55: daemon.TracePacketResponse
|
(*TraceStage)(nil), // 55: daemon.TraceStage
|
||||||
(*SubscribeRequest)(nil), // 56: daemon.SubscribeRequest
|
(*TracePacketResponse)(nil), // 56: daemon.TracePacketResponse
|
||||||
(*SystemEvent)(nil), // 57: daemon.SystemEvent
|
(*SubscribeRequest)(nil), // 57: daemon.SubscribeRequest
|
||||||
(*GetEventsRequest)(nil), // 58: daemon.GetEventsRequest
|
(*SystemEvent)(nil), // 58: daemon.SystemEvent
|
||||||
(*GetEventsResponse)(nil), // 59: daemon.GetEventsResponse
|
(*GetEventsRequest)(nil), // 59: daemon.GetEventsRequest
|
||||||
(*SwitchProfileRequest)(nil), // 60: daemon.SwitchProfileRequest
|
(*GetEventsResponse)(nil), // 60: daemon.GetEventsResponse
|
||||||
(*SwitchProfileResponse)(nil), // 61: daemon.SwitchProfileResponse
|
(*SwitchProfileRequest)(nil), // 61: daemon.SwitchProfileRequest
|
||||||
(*SetConfigRequest)(nil), // 62: daemon.SetConfigRequest
|
(*SwitchProfileResponse)(nil), // 62: daemon.SwitchProfileResponse
|
||||||
(*SetConfigResponse)(nil), // 63: daemon.SetConfigResponse
|
(*SetConfigRequest)(nil), // 63: daemon.SetConfigRequest
|
||||||
(*AddProfileRequest)(nil), // 64: daemon.AddProfileRequest
|
(*SetConfigResponse)(nil), // 64: daemon.SetConfigResponse
|
||||||
(*AddProfileResponse)(nil), // 65: daemon.AddProfileResponse
|
(*AddProfileRequest)(nil), // 65: daemon.AddProfileRequest
|
||||||
(*RemoveProfileRequest)(nil), // 66: daemon.RemoveProfileRequest
|
(*AddProfileResponse)(nil), // 66: daemon.AddProfileResponse
|
||||||
(*RemoveProfileResponse)(nil), // 67: daemon.RemoveProfileResponse
|
(*RemoveProfileRequest)(nil), // 67: daemon.RemoveProfileRequest
|
||||||
(*ListProfilesRequest)(nil), // 68: daemon.ListProfilesRequest
|
(*RemoveProfileResponse)(nil), // 68: daemon.RemoveProfileResponse
|
||||||
(*ListProfilesResponse)(nil), // 69: daemon.ListProfilesResponse
|
(*ListProfilesRequest)(nil), // 69: daemon.ListProfilesRequest
|
||||||
(*Profile)(nil), // 70: daemon.Profile
|
(*ListProfilesResponse)(nil), // 70: daemon.ListProfilesResponse
|
||||||
(*GetActiveProfileRequest)(nil), // 71: daemon.GetActiveProfileRequest
|
(*Profile)(nil), // 71: daemon.Profile
|
||||||
(*GetActiveProfileResponse)(nil), // 72: daemon.GetActiveProfileResponse
|
(*GetActiveProfileRequest)(nil), // 72: daemon.GetActiveProfileRequest
|
||||||
(*LogoutRequest)(nil), // 73: daemon.LogoutRequest
|
(*GetActiveProfileResponse)(nil), // 73: daemon.GetActiveProfileResponse
|
||||||
(*LogoutResponse)(nil), // 74: daemon.LogoutResponse
|
(*LogoutRequest)(nil), // 74: daemon.LogoutRequest
|
||||||
(*GetFeaturesRequest)(nil), // 75: daemon.GetFeaturesRequest
|
(*LogoutResponse)(nil), // 75: daemon.LogoutResponse
|
||||||
(*GetFeaturesResponse)(nil), // 76: daemon.GetFeaturesResponse
|
(*GetFeaturesRequest)(nil), // 76: daemon.GetFeaturesRequest
|
||||||
(*GetPeerSSHHostKeyRequest)(nil), // 77: daemon.GetPeerSSHHostKeyRequest
|
(*GetFeaturesResponse)(nil), // 77: daemon.GetFeaturesResponse
|
||||||
(*GetPeerSSHHostKeyResponse)(nil), // 78: daemon.GetPeerSSHHostKeyResponse
|
(*GetPeerSSHHostKeyRequest)(nil), // 78: daemon.GetPeerSSHHostKeyRequest
|
||||||
(*RequestJWTAuthRequest)(nil), // 79: daemon.RequestJWTAuthRequest
|
(*GetPeerSSHHostKeyResponse)(nil), // 79: daemon.GetPeerSSHHostKeyResponse
|
||||||
(*RequestJWTAuthResponse)(nil), // 80: daemon.RequestJWTAuthResponse
|
(*RequestJWTAuthRequest)(nil), // 80: daemon.RequestJWTAuthRequest
|
||||||
(*WaitJWTTokenRequest)(nil), // 81: daemon.WaitJWTTokenRequest
|
(*RequestJWTAuthResponse)(nil), // 81: daemon.RequestJWTAuthResponse
|
||||||
(*WaitJWTTokenResponse)(nil), // 82: daemon.WaitJWTTokenResponse
|
(*WaitJWTTokenRequest)(nil), // 82: daemon.WaitJWTTokenRequest
|
||||||
(*StartCPUProfileRequest)(nil), // 83: daemon.StartCPUProfileRequest
|
(*WaitJWTTokenResponse)(nil), // 83: daemon.WaitJWTTokenResponse
|
||||||
(*StartCPUProfileResponse)(nil), // 84: daemon.StartCPUProfileResponse
|
(*StartCPUProfileRequest)(nil), // 84: daemon.StartCPUProfileRequest
|
||||||
(*StopCPUProfileRequest)(nil), // 85: daemon.StopCPUProfileRequest
|
(*StartCPUProfileResponse)(nil), // 85: daemon.StartCPUProfileResponse
|
||||||
(*StopCPUProfileResponse)(nil), // 86: daemon.StopCPUProfileResponse
|
(*StopCPUProfileRequest)(nil), // 86: daemon.StopCPUProfileRequest
|
||||||
(*InstallerResultRequest)(nil), // 87: daemon.InstallerResultRequest
|
(*StopCPUProfileResponse)(nil), // 87: daemon.StopCPUProfileResponse
|
||||||
(*InstallerResultResponse)(nil), // 88: daemon.InstallerResultResponse
|
(*InstallerResultRequest)(nil), // 88: daemon.InstallerResultRequest
|
||||||
nil, // 89: daemon.Network.ResolvedIPsEntry
|
(*InstallerResultResponse)(nil), // 89: daemon.InstallerResultResponse
|
||||||
(*PortInfo_Range)(nil), // 90: daemon.PortInfo.Range
|
(*ExposeServiceRequest)(nil), // 90: daemon.ExposeServiceRequest
|
||||||
nil, // 91: daemon.SystemEvent.MetadataEntry
|
(*ExposeServiceEvent)(nil), // 91: daemon.ExposeServiceEvent
|
||||||
(*durationpb.Duration)(nil), // 92: google.protobuf.Duration
|
(*ExposeServiceReady)(nil), // 92: daemon.ExposeServiceReady
|
||||||
(*timestamppb.Timestamp)(nil), // 93: google.protobuf.Timestamp
|
nil, // 93: daemon.Network.ResolvedIPsEntry
|
||||||
|
(*PortInfo_Range)(nil), // 94: daemon.PortInfo.Range
|
||||||
|
nil, // 95: daemon.SystemEvent.MetadataEntry
|
||||||
|
(*durationpb.Duration)(nil), // 96: google.protobuf.Duration
|
||||||
|
(*timestamppb.Timestamp)(nil), // 97: google.protobuf.Timestamp
|
||||||
}
|
}
|
||||||
var file_daemon_proto_depIdxs = []int32{
|
var file_daemon_proto_depIdxs = []int32{
|
||||||
1, // 0: daemon.OSLifecycleRequest.type:type_name -> daemon.OSLifecycleRequest.CycleType
|
2, // 0: daemon.OSLifecycleRequest.type:type_name -> daemon.OSLifecycleRequest.CycleType
|
||||||
92, // 1: daemon.LoginRequest.dnsRouteInterval:type_name -> google.protobuf.Duration
|
96, // 1: daemon.LoginRequest.dnsRouteInterval:type_name -> google.protobuf.Duration
|
||||||
27, // 2: daemon.StatusResponse.fullStatus:type_name -> daemon.FullStatus
|
28, // 2: daemon.StatusResponse.fullStatus:type_name -> daemon.FullStatus
|
||||||
93, // 3: daemon.PeerState.connStatusUpdate:type_name -> google.protobuf.Timestamp
|
97, // 3: daemon.PeerState.connStatusUpdate:type_name -> google.protobuf.Timestamp
|
||||||
93, // 4: daemon.PeerState.lastWireguardHandshake:type_name -> google.protobuf.Timestamp
|
97, // 4: daemon.PeerState.lastWireguardHandshake:type_name -> google.protobuf.Timestamp
|
||||||
92, // 5: daemon.PeerState.latency:type_name -> google.protobuf.Duration
|
96, // 5: daemon.PeerState.latency:type_name -> google.protobuf.Duration
|
||||||
25, // 6: daemon.SSHServerState.sessions:type_name -> daemon.SSHSessionInfo
|
26, // 6: daemon.SSHServerState.sessions:type_name -> daemon.SSHSessionInfo
|
||||||
22, // 7: daemon.FullStatus.managementState:type_name -> daemon.ManagementState
|
23, // 7: daemon.FullStatus.managementState:type_name -> daemon.ManagementState
|
||||||
21, // 8: daemon.FullStatus.signalState:type_name -> daemon.SignalState
|
22, // 8: daemon.FullStatus.signalState:type_name -> daemon.SignalState
|
||||||
20, // 9: daemon.FullStatus.localPeerState:type_name -> daemon.LocalPeerState
|
21, // 9: daemon.FullStatus.localPeerState:type_name -> daemon.LocalPeerState
|
||||||
19, // 10: daemon.FullStatus.peers:type_name -> daemon.PeerState
|
20, // 10: daemon.FullStatus.peers:type_name -> daemon.PeerState
|
||||||
23, // 11: daemon.FullStatus.relays:type_name -> daemon.RelayState
|
24, // 11: daemon.FullStatus.relays:type_name -> daemon.RelayState
|
||||||
24, // 12: daemon.FullStatus.dns_servers:type_name -> daemon.NSGroupState
|
25, // 12: daemon.FullStatus.dns_servers:type_name -> daemon.NSGroupState
|
||||||
57, // 13: daemon.FullStatus.events:type_name -> daemon.SystemEvent
|
58, // 13: daemon.FullStatus.events:type_name -> daemon.SystemEvent
|
||||||
26, // 14: daemon.FullStatus.sshServerState:type_name -> daemon.SSHServerState
|
27, // 14: daemon.FullStatus.sshServerState:type_name -> daemon.SSHServerState
|
||||||
33, // 15: daemon.ListNetworksResponse.routes:type_name -> daemon.Network
|
34, // 15: daemon.ListNetworksResponse.routes:type_name -> daemon.Network
|
||||||
89, // 16: daemon.Network.resolvedIPs:type_name -> daemon.Network.ResolvedIPsEntry
|
93, // 16: daemon.Network.resolvedIPs:type_name -> daemon.Network.ResolvedIPsEntry
|
||||||
90, // 17: daemon.PortInfo.range:type_name -> daemon.PortInfo.Range
|
94, // 17: daemon.PortInfo.range:type_name -> daemon.PortInfo.Range
|
||||||
34, // 18: daemon.ForwardingRule.destinationPort:type_name -> daemon.PortInfo
|
35, // 18: daemon.ForwardingRule.destinationPort:type_name -> daemon.PortInfo
|
||||||
34, // 19: daemon.ForwardingRule.translatedPort:type_name -> daemon.PortInfo
|
35, // 19: daemon.ForwardingRule.translatedPort:type_name -> daemon.PortInfo
|
||||||
35, // 20: daemon.ForwardingRulesResponse.rules:type_name -> daemon.ForwardingRule
|
36, // 20: daemon.ForwardingRulesResponse.rules:type_name -> daemon.ForwardingRule
|
||||||
0, // 21: daemon.GetLogLevelResponse.level:type_name -> daemon.LogLevel
|
0, // 21: daemon.GetLogLevelResponse.level:type_name -> daemon.LogLevel
|
||||||
0, // 22: daemon.SetLogLevelRequest.level:type_name -> daemon.LogLevel
|
0, // 22: daemon.SetLogLevelRequest.level:type_name -> daemon.LogLevel
|
||||||
43, // 23: daemon.ListStatesResponse.states:type_name -> daemon.State
|
44, // 23: daemon.ListStatesResponse.states:type_name -> daemon.State
|
||||||
52, // 24: daemon.TracePacketRequest.tcp_flags:type_name -> daemon.TCPFlags
|
53, // 24: daemon.TracePacketRequest.tcp_flags:type_name -> daemon.TCPFlags
|
||||||
54, // 25: daemon.TracePacketResponse.stages:type_name -> daemon.TraceStage
|
55, // 25: daemon.TracePacketResponse.stages:type_name -> daemon.TraceStage
|
||||||
2, // 26: daemon.SystemEvent.severity:type_name -> daemon.SystemEvent.Severity
|
3, // 26: daemon.SystemEvent.severity:type_name -> daemon.SystemEvent.Severity
|
||||||
3, // 27: daemon.SystemEvent.category:type_name -> daemon.SystemEvent.Category
|
4, // 27: daemon.SystemEvent.category:type_name -> daemon.SystemEvent.Category
|
||||||
93, // 28: daemon.SystemEvent.timestamp:type_name -> google.protobuf.Timestamp
|
97, // 28: daemon.SystemEvent.timestamp:type_name -> google.protobuf.Timestamp
|
||||||
91, // 29: daemon.SystemEvent.metadata:type_name -> daemon.SystemEvent.MetadataEntry
|
95, // 29: daemon.SystemEvent.metadata:type_name -> daemon.SystemEvent.MetadataEntry
|
||||||
57, // 30: daemon.GetEventsResponse.events:type_name -> daemon.SystemEvent
|
58, // 30: daemon.GetEventsResponse.events:type_name -> daemon.SystemEvent
|
||||||
92, // 31: daemon.SetConfigRequest.dnsRouteInterval:type_name -> google.protobuf.Duration
|
96, // 31: daemon.SetConfigRequest.dnsRouteInterval:type_name -> google.protobuf.Duration
|
||||||
70, // 32: daemon.ListProfilesResponse.profiles:type_name -> daemon.Profile
|
71, // 32: daemon.ListProfilesResponse.profiles:type_name -> daemon.Profile
|
||||||
32, // 33: daemon.Network.ResolvedIPsEntry.value:type_name -> daemon.IPList
|
1, // 33: daemon.ExposeServiceRequest.protocol:type_name -> daemon.ExposeProtocol
|
||||||
7, // 34: daemon.DaemonService.Login:input_type -> daemon.LoginRequest
|
92, // 34: daemon.ExposeServiceEvent.ready:type_name -> daemon.ExposeServiceReady
|
||||||
9, // 35: daemon.DaemonService.WaitSSOLogin:input_type -> daemon.WaitSSOLoginRequest
|
33, // 35: daemon.Network.ResolvedIPsEntry.value:type_name -> daemon.IPList
|
||||||
11, // 36: daemon.DaemonService.Up:input_type -> daemon.UpRequest
|
8, // 36: daemon.DaemonService.Login:input_type -> daemon.LoginRequest
|
||||||
13, // 37: daemon.DaemonService.Status:input_type -> daemon.StatusRequest
|
10, // 37: daemon.DaemonService.WaitSSOLogin:input_type -> daemon.WaitSSOLoginRequest
|
||||||
15, // 38: daemon.DaemonService.Down:input_type -> daemon.DownRequest
|
12, // 38: daemon.DaemonService.Up:input_type -> daemon.UpRequest
|
||||||
17, // 39: daemon.DaemonService.GetConfig:input_type -> daemon.GetConfigRequest
|
14, // 39: daemon.DaemonService.Status:input_type -> daemon.StatusRequest
|
||||||
28, // 40: daemon.DaemonService.ListNetworks:input_type -> daemon.ListNetworksRequest
|
16, // 40: daemon.DaemonService.Down:input_type -> daemon.DownRequest
|
||||||
30, // 41: daemon.DaemonService.SelectNetworks:input_type -> daemon.SelectNetworksRequest
|
18, // 41: daemon.DaemonService.GetConfig:input_type -> daemon.GetConfigRequest
|
||||||
30, // 42: daemon.DaemonService.DeselectNetworks:input_type -> daemon.SelectNetworksRequest
|
29, // 42: daemon.DaemonService.ListNetworks:input_type -> daemon.ListNetworksRequest
|
||||||
4, // 43: daemon.DaemonService.ForwardingRules:input_type -> daemon.EmptyRequest
|
31, // 43: daemon.DaemonService.SelectNetworks:input_type -> daemon.SelectNetworksRequest
|
||||||
37, // 44: daemon.DaemonService.DebugBundle:input_type -> daemon.DebugBundleRequest
|
31, // 44: daemon.DaemonService.DeselectNetworks:input_type -> daemon.SelectNetworksRequest
|
||||||
39, // 45: daemon.DaemonService.GetLogLevel:input_type -> daemon.GetLogLevelRequest
|
5, // 45: daemon.DaemonService.ForwardingRules:input_type -> daemon.EmptyRequest
|
||||||
41, // 46: daemon.DaemonService.SetLogLevel:input_type -> daemon.SetLogLevelRequest
|
38, // 46: daemon.DaemonService.DebugBundle:input_type -> daemon.DebugBundleRequest
|
||||||
44, // 47: daemon.DaemonService.ListStates:input_type -> daemon.ListStatesRequest
|
40, // 47: daemon.DaemonService.GetLogLevel:input_type -> daemon.GetLogLevelRequest
|
||||||
46, // 48: daemon.DaemonService.CleanState:input_type -> daemon.CleanStateRequest
|
42, // 48: daemon.DaemonService.SetLogLevel:input_type -> daemon.SetLogLevelRequest
|
||||||
48, // 49: daemon.DaemonService.DeleteState:input_type -> daemon.DeleteStateRequest
|
45, // 49: daemon.DaemonService.ListStates:input_type -> daemon.ListStatesRequest
|
||||||
50, // 50: daemon.DaemonService.SetSyncResponsePersistence:input_type -> daemon.SetSyncResponsePersistenceRequest
|
47, // 50: daemon.DaemonService.CleanState:input_type -> daemon.CleanStateRequest
|
||||||
53, // 51: daemon.DaemonService.TracePacket:input_type -> daemon.TracePacketRequest
|
49, // 51: daemon.DaemonService.DeleteState:input_type -> daemon.DeleteStateRequest
|
||||||
56, // 52: daemon.DaemonService.SubscribeEvents:input_type -> daemon.SubscribeRequest
|
51, // 52: daemon.DaemonService.SetSyncResponsePersistence:input_type -> daemon.SetSyncResponsePersistenceRequest
|
||||||
58, // 53: daemon.DaemonService.GetEvents:input_type -> daemon.GetEventsRequest
|
54, // 53: daemon.DaemonService.TracePacket:input_type -> daemon.TracePacketRequest
|
||||||
60, // 54: daemon.DaemonService.SwitchProfile:input_type -> daemon.SwitchProfileRequest
|
57, // 54: daemon.DaemonService.SubscribeEvents:input_type -> daemon.SubscribeRequest
|
||||||
62, // 55: daemon.DaemonService.SetConfig:input_type -> daemon.SetConfigRequest
|
59, // 55: daemon.DaemonService.GetEvents:input_type -> daemon.GetEventsRequest
|
||||||
64, // 56: daemon.DaemonService.AddProfile:input_type -> daemon.AddProfileRequest
|
61, // 56: daemon.DaemonService.SwitchProfile:input_type -> daemon.SwitchProfileRequest
|
||||||
66, // 57: daemon.DaemonService.RemoveProfile:input_type -> daemon.RemoveProfileRequest
|
63, // 57: daemon.DaemonService.SetConfig:input_type -> daemon.SetConfigRequest
|
||||||
68, // 58: daemon.DaemonService.ListProfiles:input_type -> daemon.ListProfilesRequest
|
65, // 58: daemon.DaemonService.AddProfile:input_type -> daemon.AddProfileRequest
|
||||||
71, // 59: daemon.DaemonService.GetActiveProfile:input_type -> daemon.GetActiveProfileRequest
|
67, // 59: daemon.DaemonService.RemoveProfile:input_type -> daemon.RemoveProfileRequest
|
||||||
73, // 60: daemon.DaemonService.Logout:input_type -> daemon.LogoutRequest
|
69, // 60: daemon.DaemonService.ListProfiles:input_type -> daemon.ListProfilesRequest
|
||||||
75, // 61: daemon.DaemonService.GetFeatures:input_type -> daemon.GetFeaturesRequest
|
72, // 61: daemon.DaemonService.GetActiveProfile:input_type -> daemon.GetActiveProfileRequest
|
||||||
77, // 62: daemon.DaemonService.GetPeerSSHHostKey:input_type -> daemon.GetPeerSSHHostKeyRequest
|
74, // 62: daemon.DaemonService.Logout:input_type -> daemon.LogoutRequest
|
||||||
79, // 63: daemon.DaemonService.RequestJWTAuth:input_type -> daemon.RequestJWTAuthRequest
|
76, // 63: daemon.DaemonService.GetFeatures:input_type -> daemon.GetFeaturesRequest
|
||||||
81, // 64: daemon.DaemonService.WaitJWTToken:input_type -> daemon.WaitJWTTokenRequest
|
78, // 64: daemon.DaemonService.GetPeerSSHHostKey:input_type -> daemon.GetPeerSSHHostKeyRequest
|
||||||
83, // 65: daemon.DaemonService.StartCPUProfile:input_type -> daemon.StartCPUProfileRequest
|
80, // 65: daemon.DaemonService.RequestJWTAuth:input_type -> daemon.RequestJWTAuthRequest
|
||||||
85, // 66: daemon.DaemonService.StopCPUProfile:input_type -> daemon.StopCPUProfileRequest
|
82, // 66: daemon.DaemonService.WaitJWTToken:input_type -> daemon.WaitJWTTokenRequest
|
||||||
5, // 67: daemon.DaemonService.NotifyOSLifecycle:input_type -> daemon.OSLifecycleRequest
|
84, // 67: daemon.DaemonService.StartCPUProfile:input_type -> daemon.StartCPUProfileRequest
|
||||||
87, // 68: daemon.DaemonService.GetInstallerResult:input_type -> daemon.InstallerResultRequest
|
86, // 68: daemon.DaemonService.StopCPUProfile:input_type -> daemon.StopCPUProfileRequest
|
||||||
8, // 69: daemon.DaemonService.Login:output_type -> daemon.LoginResponse
|
6, // 69: daemon.DaemonService.NotifyOSLifecycle:input_type -> daemon.OSLifecycleRequest
|
||||||
10, // 70: daemon.DaemonService.WaitSSOLogin:output_type -> daemon.WaitSSOLoginResponse
|
88, // 70: daemon.DaemonService.GetInstallerResult:input_type -> daemon.InstallerResultRequest
|
||||||
12, // 71: daemon.DaemonService.Up:output_type -> daemon.UpResponse
|
90, // 71: daemon.DaemonService.ExposeService:input_type -> daemon.ExposeServiceRequest
|
||||||
14, // 72: daemon.DaemonService.Status:output_type -> daemon.StatusResponse
|
9, // 72: daemon.DaemonService.Login:output_type -> daemon.LoginResponse
|
||||||
16, // 73: daemon.DaemonService.Down:output_type -> daemon.DownResponse
|
11, // 73: daemon.DaemonService.WaitSSOLogin:output_type -> daemon.WaitSSOLoginResponse
|
||||||
18, // 74: daemon.DaemonService.GetConfig:output_type -> daemon.GetConfigResponse
|
13, // 74: daemon.DaemonService.Up:output_type -> daemon.UpResponse
|
||||||
29, // 75: daemon.DaemonService.ListNetworks:output_type -> daemon.ListNetworksResponse
|
15, // 75: daemon.DaemonService.Status:output_type -> daemon.StatusResponse
|
||||||
31, // 76: daemon.DaemonService.SelectNetworks:output_type -> daemon.SelectNetworksResponse
|
17, // 76: daemon.DaemonService.Down:output_type -> daemon.DownResponse
|
||||||
31, // 77: daemon.DaemonService.DeselectNetworks:output_type -> daemon.SelectNetworksResponse
|
19, // 77: daemon.DaemonService.GetConfig:output_type -> daemon.GetConfigResponse
|
||||||
36, // 78: daemon.DaemonService.ForwardingRules:output_type -> daemon.ForwardingRulesResponse
|
30, // 78: daemon.DaemonService.ListNetworks:output_type -> daemon.ListNetworksResponse
|
||||||
38, // 79: daemon.DaemonService.DebugBundle:output_type -> daemon.DebugBundleResponse
|
32, // 79: daemon.DaemonService.SelectNetworks:output_type -> daemon.SelectNetworksResponse
|
||||||
40, // 80: daemon.DaemonService.GetLogLevel:output_type -> daemon.GetLogLevelResponse
|
32, // 80: daemon.DaemonService.DeselectNetworks:output_type -> daemon.SelectNetworksResponse
|
||||||
42, // 81: daemon.DaemonService.SetLogLevel:output_type -> daemon.SetLogLevelResponse
|
37, // 81: daemon.DaemonService.ForwardingRules:output_type -> daemon.ForwardingRulesResponse
|
||||||
45, // 82: daemon.DaemonService.ListStates:output_type -> daemon.ListStatesResponse
|
39, // 82: daemon.DaemonService.DebugBundle:output_type -> daemon.DebugBundleResponse
|
||||||
47, // 83: daemon.DaemonService.CleanState:output_type -> daemon.CleanStateResponse
|
41, // 83: daemon.DaemonService.GetLogLevel:output_type -> daemon.GetLogLevelResponse
|
||||||
49, // 84: daemon.DaemonService.DeleteState:output_type -> daemon.DeleteStateResponse
|
43, // 84: daemon.DaemonService.SetLogLevel:output_type -> daemon.SetLogLevelResponse
|
||||||
51, // 85: daemon.DaemonService.SetSyncResponsePersistence:output_type -> daemon.SetSyncResponsePersistenceResponse
|
46, // 85: daemon.DaemonService.ListStates:output_type -> daemon.ListStatesResponse
|
||||||
55, // 86: daemon.DaemonService.TracePacket:output_type -> daemon.TracePacketResponse
|
48, // 86: daemon.DaemonService.CleanState:output_type -> daemon.CleanStateResponse
|
||||||
57, // 87: daemon.DaemonService.SubscribeEvents:output_type -> daemon.SystemEvent
|
50, // 87: daemon.DaemonService.DeleteState:output_type -> daemon.DeleteStateResponse
|
||||||
59, // 88: daemon.DaemonService.GetEvents:output_type -> daemon.GetEventsResponse
|
52, // 88: daemon.DaemonService.SetSyncResponsePersistence:output_type -> daemon.SetSyncResponsePersistenceResponse
|
||||||
61, // 89: daemon.DaemonService.SwitchProfile:output_type -> daemon.SwitchProfileResponse
|
56, // 89: daemon.DaemonService.TracePacket:output_type -> daemon.TracePacketResponse
|
||||||
63, // 90: daemon.DaemonService.SetConfig:output_type -> daemon.SetConfigResponse
|
58, // 90: daemon.DaemonService.SubscribeEvents:output_type -> daemon.SystemEvent
|
||||||
65, // 91: daemon.DaemonService.AddProfile:output_type -> daemon.AddProfileResponse
|
60, // 91: daemon.DaemonService.GetEvents:output_type -> daemon.GetEventsResponse
|
||||||
67, // 92: daemon.DaemonService.RemoveProfile:output_type -> daemon.RemoveProfileResponse
|
62, // 92: daemon.DaemonService.SwitchProfile:output_type -> daemon.SwitchProfileResponse
|
||||||
69, // 93: daemon.DaemonService.ListProfiles:output_type -> daemon.ListProfilesResponse
|
64, // 93: daemon.DaemonService.SetConfig:output_type -> daemon.SetConfigResponse
|
||||||
72, // 94: daemon.DaemonService.GetActiveProfile:output_type -> daemon.GetActiveProfileResponse
|
66, // 94: daemon.DaemonService.AddProfile:output_type -> daemon.AddProfileResponse
|
||||||
74, // 95: daemon.DaemonService.Logout:output_type -> daemon.LogoutResponse
|
68, // 95: daemon.DaemonService.RemoveProfile:output_type -> daemon.RemoveProfileResponse
|
||||||
76, // 96: daemon.DaemonService.GetFeatures:output_type -> daemon.GetFeaturesResponse
|
70, // 96: daemon.DaemonService.ListProfiles:output_type -> daemon.ListProfilesResponse
|
||||||
78, // 97: daemon.DaemonService.GetPeerSSHHostKey:output_type -> daemon.GetPeerSSHHostKeyResponse
|
73, // 97: daemon.DaemonService.GetActiveProfile:output_type -> daemon.GetActiveProfileResponse
|
||||||
80, // 98: daemon.DaemonService.RequestJWTAuth:output_type -> daemon.RequestJWTAuthResponse
|
75, // 98: daemon.DaemonService.Logout:output_type -> daemon.LogoutResponse
|
||||||
82, // 99: daemon.DaemonService.WaitJWTToken:output_type -> daemon.WaitJWTTokenResponse
|
77, // 99: daemon.DaemonService.GetFeatures:output_type -> daemon.GetFeaturesResponse
|
||||||
84, // 100: daemon.DaemonService.StartCPUProfile:output_type -> daemon.StartCPUProfileResponse
|
79, // 100: daemon.DaemonService.GetPeerSSHHostKey:output_type -> daemon.GetPeerSSHHostKeyResponse
|
||||||
86, // 101: daemon.DaemonService.StopCPUProfile:output_type -> daemon.StopCPUProfileResponse
|
81, // 101: daemon.DaemonService.RequestJWTAuth:output_type -> daemon.RequestJWTAuthResponse
|
||||||
6, // 102: daemon.DaemonService.NotifyOSLifecycle:output_type -> daemon.OSLifecycleResponse
|
83, // 102: daemon.DaemonService.WaitJWTToken:output_type -> daemon.WaitJWTTokenResponse
|
||||||
88, // 103: daemon.DaemonService.GetInstallerResult:output_type -> daemon.InstallerResultResponse
|
85, // 103: daemon.DaemonService.StartCPUProfile:output_type -> daemon.StartCPUProfileResponse
|
||||||
69, // [69:104] is the sub-list for method output_type
|
87, // 104: daemon.DaemonService.StopCPUProfile:output_type -> daemon.StopCPUProfileResponse
|
||||||
34, // [34:69] is the sub-list for method input_type
|
7, // 105: daemon.DaemonService.NotifyOSLifecycle:output_type -> daemon.OSLifecycleResponse
|
||||||
34, // [34:34] is the sub-list for extension type_name
|
89, // 106: daemon.DaemonService.GetInstallerResult:output_type -> daemon.InstallerResultResponse
|
||||||
34, // [34:34] is the sub-list for extension extendee
|
91, // 107: daemon.DaemonService.ExposeService:output_type -> daemon.ExposeServiceEvent
|
||||||
0, // [0:34] is the sub-list for field type_name
|
72, // [72:108] is the sub-list for method output_type
|
||||||
|
36, // [36:72] is the sub-list for method input_type
|
||||||
|
36, // [36:36] is the sub-list for extension type_name
|
||||||
|
36, // [36:36] is the sub-list for extension extendee
|
||||||
|
0, // [0:36] is the sub-list for field type_name
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() { file_daemon_proto_init() }
|
func init() { file_daemon_proto_init() }
|
||||||
@@ -6439,13 +6743,16 @@ func file_daemon_proto_init() {
|
|||||||
file_daemon_proto_msgTypes[58].OneofWrappers = []any{}
|
file_daemon_proto_msgTypes[58].OneofWrappers = []any{}
|
||||||
file_daemon_proto_msgTypes[69].OneofWrappers = []any{}
|
file_daemon_proto_msgTypes[69].OneofWrappers = []any{}
|
||||||
file_daemon_proto_msgTypes[75].OneofWrappers = []any{}
|
file_daemon_proto_msgTypes[75].OneofWrappers = []any{}
|
||||||
|
file_daemon_proto_msgTypes[86].OneofWrappers = []any{
|
||||||
|
(*ExposeServiceEvent_Ready)(nil),
|
||||||
|
}
|
||||||
type x struct{}
|
type x struct{}
|
||||||
out := protoimpl.TypeBuilder{
|
out := protoimpl.TypeBuilder{
|
||||||
File: protoimpl.DescBuilder{
|
File: protoimpl.DescBuilder{
|
||||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_daemon_proto_rawDesc), len(file_daemon_proto_rawDesc)),
|
RawDescriptor: unsafe.Slice(unsafe.StringData(file_daemon_proto_rawDesc), len(file_daemon_proto_rawDesc)),
|
||||||
NumEnums: 4,
|
NumEnums: 5,
|
||||||
NumMessages: 88,
|
NumMessages: 91,
|
||||||
NumExtensions: 0,
|
NumExtensions: 0,
|
||||||
NumServices: 1,
|
NumServices: 1,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -103,6 +103,9 @@ service DaemonService {
|
|||||||
rpc NotifyOSLifecycle(OSLifecycleRequest) returns(OSLifecycleResponse) {}
|
rpc NotifyOSLifecycle(OSLifecycleRequest) returns(OSLifecycleResponse) {}
|
||||||
|
|
||||||
rpc GetInstallerResult(InstallerResultRequest) returns (InstallerResultResponse) {}
|
rpc GetInstallerResult(InstallerResultRequest) returns (InstallerResultResponse) {}
|
||||||
|
|
||||||
|
// ExposeService exposes a local port via the NetBird reverse proxy
|
||||||
|
rpc ExposeService(ExposeServiceRequest) returns (stream ExposeServiceEvent) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -801,3 +804,32 @@ message InstallerResultResponse {
|
|||||||
bool success = 1;
|
bool success = 1;
|
||||||
string errorMsg = 2;
|
string errorMsg = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum ExposeProtocol {
|
||||||
|
EXPOSE_HTTP = 0;
|
||||||
|
EXPOSE_HTTPS = 1;
|
||||||
|
EXPOSE_TCP = 2;
|
||||||
|
EXPOSE_UDP = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ExposeServiceRequest {
|
||||||
|
uint32 port = 1;
|
||||||
|
ExposeProtocol protocol = 2;
|
||||||
|
string pin = 3;
|
||||||
|
string password = 4;
|
||||||
|
repeated string user_groups = 5;
|
||||||
|
string domain = 6;
|
||||||
|
string name_prefix = 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ExposeServiceEvent {
|
||||||
|
oneof event {
|
||||||
|
ExposeServiceReady ready = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message ExposeServiceReady {
|
||||||
|
string service_name = 1;
|
||||||
|
string service_url = 2;
|
||||||
|
string domain = 3;
|
||||||
|
}
|
||||||
|
|||||||
@@ -76,6 +76,8 @@ type DaemonServiceClient interface {
|
|||||||
StopCPUProfile(ctx context.Context, in *StopCPUProfileRequest, opts ...grpc.CallOption) (*StopCPUProfileResponse, error)
|
StopCPUProfile(ctx context.Context, in *StopCPUProfileRequest, opts ...grpc.CallOption) (*StopCPUProfileResponse, error)
|
||||||
NotifyOSLifecycle(ctx context.Context, in *OSLifecycleRequest, opts ...grpc.CallOption) (*OSLifecycleResponse, error)
|
NotifyOSLifecycle(ctx context.Context, in *OSLifecycleRequest, opts ...grpc.CallOption) (*OSLifecycleResponse, error)
|
||||||
GetInstallerResult(ctx context.Context, in *InstallerResultRequest, opts ...grpc.CallOption) (*InstallerResultResponse, error)
|
GetInstallerResult(ctx context.Context, in *InstallerResultRequest, opts ...grpc.CallOption) (*InstallerResultResponse, error)
|
||||||
|
// ExposeService exposes a local port via the NetBird reverse proxy
|
||||||
|
ExposeService(ctx context.Context, in *ExposeServiceRequest, opts ...grpc.CallOption) (DaemonService_ExposeServiceClient, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type daemonServiceClient struct {
|
type daemonServiceClient struct {
|
||||||
@@ -424,6 +426,38 @@ func (c *daemonServiceClient) GetInstallerResult(ctx context.Context, in *Instal
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *daemonServiceClient) ExposeService(ctx context.Context, in *ExposeServiceRequest, opts ...grpc.CallOption) (DaemonService_ExposeServiceClient, error) {
|
||||||
|
stream, err := c.cc.NewStream(ctx, &DaemonService_ServiceDesc.Streams[1], "/daemon.DaemonService/ExposeService", opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
x := &daemonServiceExposeServiceClient{stream}
|
||||||
|
if err := x.ClientStream.SendMsg(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := x.ClientStream.CloseSend(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return x, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type DaemonService_ExposeServiceClient interface {
|
||||||
|
Recv() (*ExposeServiceEvent, error)
|
||||||
|
grpc.ClientStream
|
||||||
|
}
|
||||||
|
|
||||||
|
type daemonServiceExposeServiceClient struct {
|
||||||
|
grpc.ClientStream
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *daemonServiceExposeServiceClient) Recv() (*ExposeServiceEvent, error) {
|
||||||
|
m := new(ExposeServiceEvent)
|
||||||
|
if err := x.ClientStream.RecvMsg(m); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
// DaemonServiceServer is the server API for DaemonService service.
|
// DaemonServiceServer is the server API for DaemonService service.
|
||||||
// All implementations must embed UnimplementedDaemonServiceServer
|
// All implementations must embed UnimplementedDaemonServiceServer
|
||||||
// for forward compatibility
|
// for forward compatibility
|
||||||
@@ -486,6 +520,8 @@ type DaemonServiceServer interface {
|
|||||||
StopCPUProfile(context.Context, *StopCPUProfileRequest) (*StopCPUProfileResponse, error)
|
StopCPUProfile(context.Context, *StopCPUProfileRequest) (*StopCPUProfileResponse, error)
|
||||||
NotifyOSLifecycle(context.Context, *OSLifecycleRequest) (*OSLifecycleResponse, error)
|
NotifyOSLifecycle(context.Context, *OSLifecycleRequest) (*OSLifecycleResponse, error)
|
||||||
GetInstallerResult(context.Context, *InstallerResultRequest) (*InstallerResultResponse, error)
|
GetInstallerResult(context.Context, *InstallerResultRequest) (*InstallerResultResponse, error)
|
||||||
|
// ExposeService exposes a local port via the NetBird reverse proxy
|
||||||
|
ExposeService(*ExposeServiceRequest, DaemonService_ExposeServiceServer) error
|
||||||
mustEmbedUnimplementedDaemonServiceServer()
|
mustEmbedUnimplementedDaemonServiceServer()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -598,6 +634,9 @@ func (UnimplementedDaemonServiceServer) NotifyOSLifecycle(context.Context, *OSLi
|
|||||||
func (UnimplementedDaemonServiceServer) GetInstallerResult(context.Context, *InstallerResultRequest) (*InstallerResultResponse, error) {
|
func (UnimplementedDaemonServiceServer) GetInstallerResult(context.Context, *InstallerResultRequest) (*InstallerResultResponse, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method GetInstallerResult not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method GetInstallerResult not implemented")
|
||||||
}
|
}
|
||||||
|
func (UnimplementedDaemonServiceServer) ExposeService(*ExposeServiceRequest, DaemonService_ExposeServiceServer) error {
|
||||||
|
return status.Errorf(codes.Unimplemented, "method ExposeService not implemented")
|
||||||
|
}
|
||||||
func (UnimplementedDaemonServiceServer) mustEmbedUnimplementedDaemonServiceServer() {}
|
func (UnimplementedDaemonServiceServer) mustEmbedUnimplementedDaemonServiceServer() {}
|
||||||
|
|
||||||
// UnsafeDaemonServiceServer may be embedded to opt out of forward compatibility for this service.
|
// UnsafeDaemonServiceServer may be embedded to opt out of forward compatibility for this service.
|
||||||
@@ -1244,6 +1283,27 @@ func _DaemonService_GetInstallerResult_Handler(srv interface{}, ctx context.Cont
|
|||||||
return interceptor(ctx, in, info, handler)
|
return interceptor(ctx, in, info, handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func _DaemonService_ExposeService_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||||
|
m := new(ExposeServiceRequest)
|
||||||
|
if err := stream.RecvMsg(m); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return srv.(DaemonServiceServer).ExposeService(m, &daemonServiceExposeServiceServer{stream})
|
||||||
|
}
|
||||||
|
|
||||||
|
type DaemonService_ExposeServiceServer interface {
|
||||||
|
Send(*ExposeServiceEvent) error
|
||||||
|
grpc.ServerStream
|
||||||
|
}
|
||||||
|
|
||||||
|
type daemonServiceExposeServiceServer struct {
|
||||||
|
grpc.ServerStream
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *daemonServiceExposeServiceServer) Send(m *ExposeServiceEvent) error {
|
||||||
|
return x.ServerStream.SendMsg(m)
|
||||||
|
}
|
||||||
|
|
||||||
// DaemonService_ServiceDesc is the grpc.ServiceDesc for DaemonService service.
|
// DaemonService_ServiceDesc is the grpc.ServiceDesc for DaemonService service.
|
||||||
// It's only intended for direct use with grpc.RegisterService,
|
// It's only intended for direct use with grpc.RegisterService,
|
||||||
// and not to be introspected or modified (even as a copy)
|
// and not to be introspected or modified (even as a copy)
|
||||||
@@ -1394,6 +1454,11 @@ var DaemonService_ServiceDesc = grpc.ServiceDesc{
|
|||||||
Handler: _DaemonService_SubscribeEvents_Handler,
|
Handler: _DaemonService_SubscribeEvents_Handler,
|
||||||
ServerStreams: true,
|
ServerStreams: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
StreamName: "ExposeService",
|
||||||
|
Handler: _DaemonService_ExposeService_Handler,
|
||||||
|
ServerStreams: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Metadata: "daemon.proto",
|
Metadata: "daemon.proto",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,77 +0,0 @@
|
|||||||
package server
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/internal"
|
|
||||||
"github.com/netbirdio/netbird/client/proto"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NotifyOSLifecycle handles operating system lifecycle events by executing appropriate logic based on the request type.
|
|
||||||
func (s *Server) NotifyOSLifecycle(callerCtx context.Context, req *proto.OSLifecycleRequest) (*proto.OSLifecycleResponse, error) {
|
|
||||||
switch req.GetType() {
|
|
||||||
case proto.OSLifecycleRequest_WAKEUP:
|
|
||||||
return s.handleWakeUp(callerCtx)
|
|
||||||
case proto.OSLifecycleRequest_SLEEP:
|
|
||||||
return s.handleSleep(callerCtx)
|
|
||||||
default:
|
|
||||||
log.Errorf("unknown OSLifecycleRequest type: %v", req.GetType())
|
|
||||||
}
|
|
||||||
return &proto.OSLifecycleResponse{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// handleWakeUp processes a wake-up event by triggering the Up command if the system was previously put to sleep.
|
|
||||||
// It resets the sleep state and logs the process. Returns a response or an error if the Up command fails.
|
|
||||||
func (s *Server) handleWakeUp(callerCtx context.Context) (*proto.OSLifecycleResponse, error) {
|
|
||||||
if !s.sleepTriggeredDown.Load() {
|
|
||||||
log.Info("skipping up because wasn't sleep down")
|
|
||||||
return &proto.OSLifecycleResponse{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// avoid other wakeup runs if sleep didn't make the computer sleep
|
|
||||||
s.sleepTriggeredDown.Store(false)
|
|
||||||
|
|
||||||
log.Info("running up after wake up")
|
|
||||||
_, err := s.Up(callerCtx, &proto.UpRequest{})
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("running up failed: %v", err)
|
|
||||||
return &proto.OSLifecycleResponse{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info("running up command executed successfully")
|
|
||||||
return &proto.OSLifecycleResponse{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// handleSleep handles the sleep event by initiating a "down" sequence if the system is in a connected or connecting state.
|
|
||||||
func (s *Server) handleSleep(callerCtx context.Context) (*proto.OSLifecycleResponse, error) {
|
|
||||||
s.mutex.Lock()
|
|
||||||
|
|
||||||
state := internal.CtxGetState(s.rootCtx)
|
|
||||||
status, err := state.Status()
|
|
||||||
if err != nil {
|
|
||||||
s.mutex.Unlock()
|
|
||||||
return &proto.OSLifecycleResponse{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if status != internal.StatusConnecting && status != internal.StatusConnected {
|
|
||||||
log.Infof("skipping setting the agent down because status is %s", status)
|
|
||||||
s.mutex.Unlock()
|
|
||||||
return &proto.OSLifecycleResponse{}, nil
|
|
||||||
}
|
|
||||||
s.mutex.Unlock()
|
|
||||||
|
|
||||||
log.Info("running down after system started sleeping")
|
|
||||||
|
|
||||||
_, err = s.Down(callerCtx, &proto.DownRequest{})
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("running down failed: %v", err)
|
|
||||||
return &proto.OSLifecycleResponse{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
s.sleepTriggeredDown.Store(true)
|
|
||||||
|
|
||||||
log.Info("running down executed successfully")
|
|
||||||
return &proto.OSLifecycleResponse{}, nil
|
|
||||||
}
|
|
||||||
@@ -1,219 +0,0 @@
|
|||||||
package server
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/internal"
|
|
||||||
"github.com/netbirdio/netbird/client/internal/peer"
|
|
||||||
"github.com/netbirdio/netbird/client/proto"
|
|
||||||
)
|
|
||||||
|
|
||||||
func newTestServer() *Server {
|
|
||||||
ctx := internal.CtxInitState(context.Background())
|
|
||||||
return &Server{
|
|
||||||
rootCtx: ctx,
|
|
||||||
statusRecorder: peer.NewRecorder(""),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNotifyOSLifecycle_WakeUp_SkipsWhenNotSleepTriggered(t *testing.T) {
|
|
||||||
s := newTestServer()
|
|
||||||
|
|
||||||
// sleepTriggeredDown is false by default
|
|
||||||
assert.False(t, s.sleepTriggeredDown.Load())
|
|
||||||
|
|
||||||
resp, err := s.NotifyOSLifecycle(context.Background(), &proto.OSLifecycleRequest{
|
|
||||||
Type: proto.OSLifecycleRequest_WAKEUP,
|
|
||||||
})
|
|
||||||
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NotNil(t, resp)
|
|
||||||
assert.False(t, s.sleepTriggeredDown.Load(), "flag should remain false")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNotifyOSLifecycle_Sleep_SkipsWhenStatusIdle(t *testing.T) {
|
|
||||||
s := newTestServer()
|
|
||||||
|
|
||||||
state := internal.CtxGetState(s.rootCtx)
|
|
||||||
state.Set(internal.StatusIdle)
|
|
||||||
|
|
||||||
resp, err := s.NotifyOSLifecycle(context.Background(), &proto.OSLifecycleRequest{
|
|
||||||
Type: proto.OSLifecycleRequest_SLEEP,
|
|
||||||
})
|
|
||||||
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NotNil(t, resp)
|
|
||||||
assert.False(t, s.sleepTriggeredDown.Load(), "flag should remain false when status is Idle")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNotifyOSLifecycle_Sleep_SkipsWhenStatusNeedsLogin(t *testing.T) {
|
|
||||||
s := newTestServer()
|
|
||||||
|
|
||||||
state := internal.CtxGetState(s.rootCtx)
|
|
||||||
state.Set(internal.StatusNeedsLogin)
|
|
||||||
|
|
||||||
resp, err := s.NotifyOSLifecycle(context.Background(), &proto.OSLifecycleRequest{
|
|
||||||
Type: proto.OSLifecycleRequest_SLEEP,
|
|
||||||
})
|
|
||||||
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NotNil(t, resp)
|
|
||||||
assert.False(t, s.sleepTriggeredDown.Load(), "flag should remain false when status is NeedsLogin")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNotifyOSLifecycle_Sleep_SetsFlag_WhenConnecting(t *testing.T) {
|
|
||||||
s := newTestServer()
|
|
||||||
|
|
||||||
state := internal.CtxGetState(s.rootCtx)
|
|
||||||
state.Set(internal.StatusConnecting)
|
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
defer cancel()
|
|
||||||
s.actCancel = cancel
|
|
||||||
|
|
||||||
resp, err := s.NotifyOSLifecycle(ctx, &proto.OSLifecycleRequest{
|
|
||||||
Type: proto.OSLifecycleRequest_SLEEP,
|
|
||||||
})
|
|
||||||
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.NotNil(t, resp, "handleSleep returns not nil response on success")
|
|
||||||
assert.True(t, s.sleepTriggeredDown.Load(), "flag should be set after sleep when connecting")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNotifyOSLifecycle_Sleep_SetsFlag_WhenConnected(t *testing.T) {
|
|
||||||
s := newTestServer()
|
|
||||||
|
|
||||||
state := internal.CtxGetState(s.rootCtx)
|
|
||||||
state.Set(internal.StatusConnected)
|
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
defer cancel()
|
|
||||||
s.actCancel = cancel
|
|
||||||
|
|
||||||
resp, err := s.NotifyOSLifecycle(ctx, &proto.OSLifecycleRequest{
|
|
||||||
Type: proto.OSLifecycleRequest_SLEEP,
|
|
||||||
})
|
|
||||||
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.NotNil(t, resp, "handleSleep returns not nil response on success")
|
|
||||||
assert.True(t, s.sleepTriggeredDown.Load(), "flag should be set after sleep when connected")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNotifyOSLifecycle_WakeUp_ResetsFlag(t *testing.T) {
|
|
||||||
s := newTestServer()
|
|
||||||
|
|
||||||
// Manually set the flag to simulate prior sleep down
|
|
||||||
s.sleepTriggeredDown.Store(true)
|
|
||||||
|
|
||||||
// WakeUp will try to call Up which fails without proper setup, but flag should reset first
|
|
||||||
_, _ = s.NotifyOSLifecycle(context.Background(), &proto.OSLifecycleRequest{
|
|
||||||
Type: proto.OSLifecycleRequest_WAKEUP,
|
|
||||||
})
|
|
||||||
|
|
||||||
assert.False(t, s.sleepTriggeredDown.Load(), "flag should be reset after WakeUp attempt")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNotifyOSLifecycle_MultipleWakeUpCalls(t *testing.T) {
|
|
||||||
s := newTestServer()
|
|
||||||
|
|
||||||
// First wakeup without prior sleep - should be no-op
|
|
||||||
resp, err := s.NotifyOSLifecycle(context.Background(), &proto.OSLifecycleRequest{
|
|
||||||
Type: proto.OSLifecycleRequest_WAKEUP,
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NotNil(t, resp)
|
|
||||||
assert.False(t, s.sleepTriggeredDown.Load())
|
|
||||||
|
|
||||||
// Simulate prior sleep
|
|
||||||
s.sleepTriggeredDown.Store(true)
|
|
||||||
|
|
||||||
// First wakeup after sleep - should reset flag
|
|
||||||
_, _ = s.NotifyOSLifecycle(context.Background(), &proto.OSLifecycleRequest{
|
|
||||||
Type: proto.OSLifecycleRequest_WAKEUP,
|
|
||||||
})
|
|
||||||
assert.False(t, s.sleepTriggeredDown.Load())
|
|
||||||
|
|
||||||
// Second wakeup - should be no-op
|
|
||||||
resp, err = s.NotifyOSLifecycle(context.Background(), &proto.OSLifecycleRequest{
|
|
||||||
Type: proto.OSLifecycleRequest_WAKEUP,
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NotNil(t, resp)
|
|
||||||
assert.False(t, s.sleepTriggeredDown.Load())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHandleWakeUp_SkipsWhenFlagFalse(t *testing.T) {
|
|
||||||
s := newTestServer()
|
|
||||||
|
|
||||||
resp, err := s.handleWakeUp(context.Background())
|
|
||||||
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NotNil(t, resp)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHandleWakeUp_ResetsFlagBeforeUp(t *testing.T) {
|
|
||||||
s := newTestServer()
|
|
||||||
s.sleepTriggeredDown.Store(true)
|
|
||||||
|
|
||||||
// Even if Up fails, flag should be reset
|
|
||||||
_, _ = s.handleWakeUp(context.Background())
|
|
||||||
|
|
||||||
assert.False(t, s.sleepTriggeredDown.Load(), "flag must be reset before calling Up")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHandleSleep_SkipsForNonActiveStates(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
status internal.StatusType
|
|
||||||
}{
|
|
||||||
{"Idle", internal.StatusIdle},
|
|
||||||
{"NeedsLogin", internal.StatusNeedsLogin},
|
|
||||||
{"LoginFailed", internal.StatusLoginFailed},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
s := newTestServer()
|
|
||||||
state := internal.CtxGetState(s.rootCtx)
|
|
||||||
state.Set(tt.status)
|
|
||||||
|
|
||||||
resp, err := s.handleSleep(context.Background())
|
|
||||||
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NotNil(t, resp)
|
|
||||||
assert.False(t, s.sleepTriggeredDown.Load())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHandleSleep_ProceedsForActiveStates(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
status internal.StatusType
|
|
||||||
}{
|
|
||||||
{"Connecting", internal.StatusConnecting},
|
|
||||||
{"Connected", internal.StatusConnected},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
s := newTestServer()
|
|
||||||
state := internal.CtxGetState(s.rootCtx)
|
|
||||||
state.Set(tt.status)
|
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
defer cancel()
|
|
||||||
s.actCancel = cancel
|
|
||||||
|
|
||||||
resp, err := s.handleSleep(ctx)
|
|
||||||
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.NotNil(t, resp)
|
|
||||||
assert.True(t, s.sleepTriggeredDown.Load())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+62
-3
@@ -21,7 +21,9 @@ import (
|
|||||||
gstatus "google.golang.org/grpc/status"
|
gstatus "google.golang.org/grpc/status"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/internal/auth"
|
"github.com/netbirdio/netbird/client/internal/auth"
|
||||||
|
"github.com/netbirdio/netbird/client/internal/expose"
|
||||||
"github.com/netbirdio/netbird/client/internal/profilemanager"
|
"github.com/netbirdio/netbird/client/internal/profilemanager"
|
||||||
|
sleephandler "github.com/netbirdio/netbird/client/internal/sleep/handler"
|
||||||
"github.com/netbirdio/netbird/client/system"
|
"github.com/netbirdio/netbird/client/system"
|
||||||
mgm "github.com/netbirdio/netbird/shared/management/client"
|
mgm "github.com/netbirdio/netbird/shared/management/client"
|
||||||
"github.com/netbirdio/netbird/shared/management/domain"
|
"github.com/netbirdio/netbird/shared/management/domain"
|
||||||
@@ -85,8 +87,7 @@ type Server struct {
|
|||||||
profilesDisabled bool
|
profilesDisabled bool
|
||||||
updateSettingsDisabled bool
|
updateSettingsDisabled bool
|
||||||
|
|
||||||
// sleepTriggeredDown holds a state indicated if the sleep handler triggered the last client down
|
sleepHandler *sleephandler.SleepHandler
|
||||||
sleepTriggeredDown atomic.Bool
|
|
||||||
|
|
||||||
jwtCache *jwtCache
|
jwtCache *jwtCache
|
||||||
}
|
}
|
||||||
@@ -100,7 +101,7 @@ type oauthAuthFlow struct {
|
|||||||
|
|
||||||
// New server instance constructor.
|
// New server instance constructor.
|
||||||
func New(ctx context.Context, logFile string, configFile string, profilesDisabled bool, updateSettingsDisabled bool) *Server {
|
func New(ctx context.Context, logFile string, configFile string, profilesDisabled bool, updateSettingsDisabled bool) *Server {
|
||||||
return &Server{
|
s := &Server{
|
||||||
rootCtx: ctx,
|
rootCtx: ctx,
|
||||||
logFile: logFile,
|
logFile: logFile,
|
||||||
persistSyncResponse: true,
|
persistSyncResponse: true,
|
||||||
@@ -110,6 +111,10 @@ func New(ctx context.Context, logFile string, configFile string, profilesDisable
|
|||||||
updateSettingsDisabled: updateSettingsDisabled,
|
updateSettingsDisabled: updateSettingsDisabled,
|
||||||
jwtCache: newJWTCache(),
|
jwtCache: newJWTCache(),
|
||||||
}
|
}
|
||||||
|
agent := &serverAgent{s}
|
||||||
|
s.sleepHandler = sleephandler.New(agent)
|
||||||
|
|
||||||
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) Start() error {
|
func (s *Server) Start() error {
|
||||||
@@ -1312,6 +1317,60 @@ func (s *Server) WaitJWTToken(
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ExposeService exposes a local port via the NetBird reverse proxy.
|
||||||
|
func (s *Server) ExposeService(req *proto.ExposeServiceRequest, srv proto.DaemonService_ExposeServiceServer) error {
|
||||||
|
s.mutex.Lock()
|
||||||
|
if !s.clientRunning {
|
||||||
|
s.mutex.Unlock()
|
||||||
|
return gstatus.Errorf(codes.FailedPrecondition, "client is not running, run 'netbird up' first")
|
||||||
|
}
|
||||||
|
connectClient := s.connectClient
|
||||||
|
s.mutex.Unlock()
|
||||||
|
|
||||||
|
if connectClient == nil {
|
||||||
|
return gstatus.Errorf(codes.FailedPrecondition, "client not initialized")
|
||||||
|
}
|
||||||
|
|
||||||
|
engine := connectClient.Engine()
|
||||||
|
if engine == nil {
|
||||||
|
return gstatus.Errorf(codes.FailedPrecondition, "engine not initialized")
|
||||||
|
}
|
||||||
|
|
||||||
|
mgr := engine.GetExposeManager()
|
||||||
|
if mgr == nil {
|
||||||
|
return gstatus.Errorf(codes.Internal, "expose manager not available")
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := srv.Context()
|
||||||
|
|
||||||
|
exposeCtx, exposeCancel := context.WithTimeout(ctx, 30*time.Second)
|
||||||
|
defer exposeCancel()
|
||||||
|
|
||||||
|
mgmReq := expose.NewRequest(req)
|
||||||
|
result, err := mgr.Expose(exposeCtx, *mgmReq)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := srv.Send(&proto.ExposeServiceEvent{
|
||||||
|
Event: &proto.ExposeServiceEvent_Ready{
|
||||||
|
Ready: &proto.ExposeServiceReady{
|
||||||
|
ServiceName: result.ServiceName,
|
||||||
|
ServiceUrl: result.ServiceURL,
|
||||||
|
Domain: result.Domain,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = mgr.KeepAlive(ctx, result.Domain)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func isUnixRunningDesktop() bool {
|
func isUnixRunningDesktop() bool {
|
||||||
if runtime.GOOS != "linux" && runtime.GOOS != "freebsd" {
|
if runtime.GOOS != "linux" && runtime.GOOS != "freebsd" {
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/client/internal"
|
||||||
|
"github.com/netbirdio/netbird/client/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
// serverAgent adapts Server to the handler.Agent and handler.StatusChecker interfaces
|
||||||
|
type serverAgent struct {
|
||||||
|
s *Server
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *serverAgent) Up(ctx context.Context) error {
|
||||||
|
_, err := a.s.Up(ctx, &proto.UpRequest{})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *serverAgent) Down(ctx context.Context) error {
|
||||||
|
_, err := a.s.Down(ctx, &proto.DownRequest{})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *serverAgent) Status() (internal.StatusType, error) {
|
||||||
|
return internal.CtxGetState(a.s.rootCtx).Status()
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotifyOSLifecycle handles operating system lifecycle events by executing appropriate logic based on the request type.
|
||||||
|
func (s *Server) NotifyOSLifecycle(callerCtx context.Context, req *proto.OSLifecycleRequest) (*proto.OSLifecycleResponse, error) {
|
||||||
|
switch req.GetType() {
|
||||||
|
case proto.OSLifecycleRequest_WAKEUP:
|
||||||
|
if err := s.sleepHandler.HandleWakeUp(callerCtx); err != nil {
|
||||||
|
return &proto.OSLifecycleResponse{}, err
|
||||||
|
}
|
||||||
|
case proto.OSLifecycleRequest_SLEEP:
|
||||||
|
if err := s.sleepHandler.HandleSleep(callerCtx); err != nil {
|
||||||
|
return &proto.OSLifecycleResponse{}, err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
log.Errorf("unknown OSLifecycleRequest type: %v", req.GetType())
|
||||||
|
}
|
||||||
|
return &proto.OSLifecycleResponse{}, nil
|
||||||
|
}
|
||||||
@@ -9,4 +9,5 @@ type Manager interface {
|
|||||||
CreateDomain(ctx context.Context, accountID, userID, domainName, targetCluster string) (*Domain, error)
|
CreateDomain(ctx context.Context, accountID, userID, domainName, targetCluster string) (*Domain, error)
|
||||||
DeleteDomain(ctx context.Context, accountID, userID, domainID string) error
|
DeleteDomain(ctx context.Context, accountID, userID, domainID string) error
|
||||||
ValidateDomain(ctx context.Context, accountID, userID, domainID string)
|
ValidateDomain(ctx context.Context, accountID, userID, domainID string)
|
||||||
|
GetClusterDomains() []string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -228,6 +228,18 @@ func (m Manager) ValidateDomain(ctx context.Context, accountID, userID, domainID
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetClusterDomains returns a list of proxy cluster domains.
|
||||||
|
func (m Manager) GetClusterDomains() []string {
|
||||||
|
if m.proxyManager == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
addresses, err := m.proxyManager.GetActiveClusterAddresses(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return addresses
|
||||||
|
}
|
||||||
|
|
||||||
// DeriveClusterFromDomain determines the proxy cluster for a given domain.
|
// DeriveClusterFromDomain determines the proxy cluster for a given domain.
|
||||||
// For free domains (those ending with a known cluster suffix), the cluster is extracted from the domain.
|
// For free domains (those ending with a known cluster suffix), the cluster is extracted from the domain.
|
||||||
// For custom domains, the cluster is determined by checking the registered custom domain's target cluster.
|
// For custom domains, the cluster is determined by checking the registered custom domain's target cluster.
|
||||||
|
|||||||
@@ -23,6 +23,10 @@ type Manager interface {
|
|||||||
GetServiceByID(ctx context.Context, accountID, serviceID string) (*Service, error)
|
GetServiceByID(ctx context.Context, accountID, serviceID string) (*Service, error)
|
||||||
GetAccountServices(ctx context.Context, accountID string) ([]*Service, error)
|
GetAccountServices(ctx context.Context, accountID string) ([]*Service, error)
|
||||||
GetServiceIDByTargetID(ctx context.Context, accountID string, resourceID string) (string, error)
|
GetServiceIDByTargetID(ctx context.Context, accountID string, resourceID string) (string, error)
|
||||||
|
CreateServiceFromPeer(ctx context.Context, accountID, peerID string, req *ExposeServiceRequest) (*ExposeServiceResponse, error)
|
||||||
|
RenewServiceFromPeer(ctx context.Context, accountID, peerID, domain string) error
|
||||||
|
StopServiceFromPeer(ctx context.Context, accountID, peerID, domain string) error
|
||||||
|
StartExposeReaper(ctx context.Context)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProxyController is responsible for managing proxy clusters and routing service updates.
|
// ProxyController is responsible for managing proxy clusters and routing service updates.
|
||||||
|
|||||||
@@ -50,6 +50,21 @@ func (mr *MockManagerMockRecorder) CreateService(ctx, accountID, userID, service
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateService", reflect.TypeOf((*MockManager)(nil).CreateService), ctx, accountID, userID, service)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateService", reflect.TypeOf((*MockManager)(nil).CreateService), ctx, accountID, userID, service)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateServiceFromPeer mocks base method.
|
||||||
|
func (m *MockManager) CreateServiceFromPeer(ctx context.Context, accountID, peerID string, req *ExposeServiceRequest) (*ExposeServiceResponse, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "CreateServiceFromPeer", ctx, accountID, peerID, req)
|
||||||
|
ret0, _ := ret[0].(*ExposeServiceResponse)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateServiceFromPeer indicates an expected call of CreateServiceFromPeer.
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
// DeleteAllServices mocks base method.
|
// DeleteAllServices mocks base method.
|
||||||
func (m *MockManager) DeleteAllServices(ctx context.Context, accountID, userID string) error {
|
func (m *MockManager) DeleteAllServices(ctx context.Context, accountID, userID string) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
@@ -196,6 +211,20 @@ func (mr *MockManagerMockRecorder) ReloadService(ctx, accountID, serviceID inter
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReloadService", reflect.TypeOf((*MockManager)(nil).ReloadService), ctx, accountID, serviceID)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReloadService", reflect.TypeOf((*MockManager)(nil).ReloadService), ctx, accountID, serviceID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RenewServiceFromPeer mocks base method.
|
||||||
|
func (m *MockManager) RenewServiceFromPeer(ctx context.Context, accountID, peerID, domain string) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "RenewServiceFromPeer", ctx, accountID, peerID, domain)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// RenewServiceFromPeer indicates an expected call of RenewServiceFromPeer.
|
||||||
|
func (mr *MockManagerMockRecorder) RenewServiceFromPeer(ctx, accountID, peerID, domain interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RenewServiceFromPeer", reflect.TypeOf((*MockManager)(nil).RenewServiceFromPeer), ctx, accountID, peerID, domain)
|
||||||
|
}
|
||||||
|
|
||||||
// SetCertificateIssuedAt mocks base method.
|
// SetCertificateIssuedAt mocks base method.
|
||||||
func (m *MockManager) SetCertificateIssuedAt(ctx context.Context, accountID, serviceID string) error {
|
func (m *MockManager) SetCertificateIssuedAt(ctx context.Context, accountID, serviceID string) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
@@ -224,6 +253,32 @@ func (mr *MockManagerMockRecorder) SetStatus(ctx, accountID, serviceID, status i
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetStatus", reflect.TypeOf((*MockManager)(nil).SetStatus), ctx, accountID, serviceID, status)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetStatus", reflect.TypeOf((*MockManager)(nil).SetStatus), ctx, accountID, serviceID, status)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StartExposeReaper mocks base method.
|
||||||
|
func (m *MockManager) StartExposeReaper(ctx context.Context) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
m.ctrl.Call(m, "StartExposeReaper", ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartExposeReaper indicates an expected call of StartExposeReaper.
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StopServiceFromPeer mocks base method.
|
||||||
|
func (m *MockManager) StopServiceFromPeer(ctx context.Context, accountID, peerID, domain string) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "StopServiceFromPeer", ctx, accountID, peerID, domain)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// StopServiceFromPeer indicates an expected call of StopServiceFromPeer.
|
||||||
|
func (mr *MockManagerMockRecorder) StopServiceFromPeer(ctx, accountID, peerID, domain interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StopServiceFromPeer", reflect.TypeOf((*MockManager)(nil).StopServiceFromPeer), ctx, accountID, peerID, domain)
|
||||||
|
}
|
||||||
|
|
||||||
// UpdateService mocks base method.
|
// UpdateService mocks base method.
|
||||||
func (m *MockManager) UpdateService(ctx context.Context, accountID, userID string, service *Service) (*Service, error) {
|
func (m *MockManager) UpdateService(ctx context.Context, accountID, userID string, service *Service) (*Service, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
|
|||||||
@@ -0,0 +1,163 @@
|
|||||||
|
package manager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/shared/management/status"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
exposeTTL = 90 * time.Second
|
||||||
|
exposeReapInterval = 30 * time.Second
|
||||||
|
maxExposesPerPeer = 10
|
||||||
|
)
|
||||||
|
|
||||||
|
type trackedExpose struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
domain string
|
||||||
|
accountID string
|
||||||
|
peerID string
|
||||||
|
lastRenewed time.Time
|
||||||
|
expiring bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type exposeTracker struct {
|
||||||
|
activeExposes sync.Map
|
||||||
|
exposeCreateMu sync.Mutex
|
||||||
|
manager *Manager
|
||||||
|
}
|
||||||
|
|
||||||
|
func exposeKey(peerID, domain string) string {
|
||||||
|
return peerID + ":" + domain
|
||||||
|
}
|
||||||
|
|
||||||
|
// TrackExposeIfAllowed atomically checks the per-peer limit and registers a new
|
||||||
|
// active expose session under the same lock. Returns (true, false) if the expose
|
||||||
|
// was already tracked (duplicate), (false, true) if tracking succeeded, and
|
||||||
|
// (false, false) if the peer has reached the limit.
|
||||||
|
func (t *exposeTracker) TrackExposeIfAllowed(peerID, domain, accountID string) (alreadyTracked, ok bool) {
|
||||||
|
t.exposeCreateMu.Lock()
|
||||||
|
defer t.exposeCreateMu.Unlock()
|
||||||
|
|
||||||
|
key := exposeKey(peerID, domain)
|
||||||
|
_, loaded := t.activeExposes.LoadOrStore(key, &trackedExpose{
|
||||||
|
domain: domain,
|
||||||
|
accountID: accountID,
|
||||||
|
peerID: peerID,
|
||||||
|
lastRenewed: time.Now(),
|
||||||
|
})
|
||||||
|
if loaded {
|
||||||
|
return true, false
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.CountPeerExposes(peerID) > maxExposesPerPeer {
|
||||||
|
t.activeExposes.Delete(key)
|
||||||
|
return false, false
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// UntrackExpose removes an active expose session from tracking.
|
||||||
|
func (t *exposeTracker) UntrackExpose(peerID, domain string) {
|
||||||
|
t.activeExposes.Delete(exposeKey(peerID, domain))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CountPeerExposes returns the number of active expose sessions for a peer.
|
||||||
|
func (t *exposeTracker) CountPeerExposes(peerID string) int {
|
||||||
|
count := 0
|
||||||
|
t.activeExposes.Range(func(_, val any) bool {
|
||||||
|
if expose := val.(*trackedExpose); expose.peerID == peerID {
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaxExposesPerPeer returns the maximum number of concurrent exposes allowed per peer.
|
||||||
|
func (t *exposeTracker) MaxExposesPerPeer() int {
|
||||||
|
return maxExposesPerPeer
|
||||||
|
}
|
||||||
|
|
||||||
|
// RenewTrackedExpose updates the in-memory lastRenewed timestamp for a tracked expose.
|
||||||
|
// Returns false if the expose is not tracked or is being reaped.
|
||||||
|
func (t *exposeTracker) RenewTrackedExpose(peerID, domain string) bool {
|
||||||
|
key := exposeKey(peerID, domain)
|
||||||
|
val, ok := t.activeExposes.Load(key)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
expose := val.(*trackedExpose)
|
||||||
|
expose.mu.Lock()
|
||||||
|
if expose.expiring {
|
||||||
|
expose.mu.Unlock()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
expose.lastRenewed = time.Now()
|
||||||
|
expose.mu.Unlock()
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// StopTrackedExpose removes an active expose session from tracking.
|
||||||
|
// Returns false if the expose was not tracked.
|
||||||
|
func (t *exposeTracker) StopTrackedExpose(peerID, domain string) bool {
|
||||||
|
key := exposeKey(peerID, domain)
|
||||||
|
_, ok := t.activeExposes.LoadAndDelete(key)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartExposeReaper starts a background goroutine that reaps expired expose sessions.
|
||||||
|
func (t *exposeTracker) StartExposeReaper(ctx context.Context) {
|
||||||
|
go func() {
|
||||||
|
ticker := time.NewTicker(exposeReapInterval)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case <-ticker.C:
|
||||||
|
t.reapExpiredExposes()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *exposeTracker) reapExpiredExposes() {
|
||||||
|
t.activeExposes.Range(func(key, val any) bool {
|
||||||
|
expose := val.(*trackedExpose)
|
||||||
|
expose.mu.Lock()
|
||||||
|
expired := time.Since(expose.lastRenewed) > exposeTTL
|
||||||
|
if expired {
|
||||||
|
expose.expiring = true
|
||||||
|
}
|
||||||
|
expose.mu.Unlock()
|
||||||
|
|
||||||
|
if !expired {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("reaping expired expose session for peer %s, domain %s", expose.peerID, expose.domain)
|
||||||
|
|
||||||
|
err := t.manager.deleteServiceFromPeer(context.Background(), expose.accountID, expose.peerID, expose.domain, true)
|
||||||
|
|
||||||
|
s, _ := status.FromError(err)
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case err == nil:
|
||||||
|
t.activeExposes.Delete(key)
|
||||||
|
case s.ErrorType == status.NotFound:
|
||||||
|
log.Debugf("service %s was already deleted", expose.domain)
|
||||||
|
default:
|
||||||
|
log.Errorf("failed to delete expired peer-exposed service for domain %s: %v", expose.domain, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,256 @@
|
|||||||
|
package manager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
rpservice "github.com/netbirdio/netbird/management/internals/modules/reverseproxy/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestExposeKey(t *testing.T) {
|
||||||
|
assert.Equal(t, "peer1:example.com", exposeKey("peer1", "example.com"))
|
||||||
|
assert.Equal(t, "peer2:other.com", exposeKey("peer2", "other.com"))
|
||||||
|
assert.NotEqual(t, exposeKey("peer1", "a.com"), exposeKey("peer1", "b.com"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTrackExposeIfAllowed(t *testing.T) {
|
||||||
|
t.Run("first track succeeds", func(t *testing.T) {
|
||||||
|
tracker := &exposeTracker{}
|
||||||
|
alreadyTracked, ok := tracker.TrackExposeIfAllowed("peer1", "a.com", "acct1")
|
||||||
|
assert.False(t, alreadyTracked, "first track should not be duplicate")
|
||||||
|
assert.True(t, ok, "first track should be allowed")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("duplicate track detected", func(t *testing.T) {
|
||||||
|
tracker := &exposeTracker{}
|
||||||
|
tracker.TrackExposeIfAllowed("peer1", "a.com", "acct1")
|
||||||
|
|
||||||
|
alreadyTracked, ok := tracker.TrackExposeIfAllowed("peer1", "a.com", "acct1")
|
||||||
|
assert.True(t, alreadyTracked, "second track should be duplicate")
|
||||||
|
assert.False(t, ok)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("rejects when at limit", func(t *testing.T) {
|
||||||
|
tracker := &exposeTracker{}
|
||||||
|
for i := range maxExposesPerPeer {
|
||||||
|
_, ok := tracker.TrackExposeIfAllowed("peer1", "domain-"+string(rune('a'+i))+".com", "acct1")
|
||||||
|
assert.True(t, ok, "track %d should be allowed", i)
|
||||||
|
}
|
||||||
|
|
||||||
|
alreadyTracked, ok := tracker.TrackExposeIfAllowed("peer1", "over-limit.com", "acct1")
|
||||||
|
assert.False(t, alreadyTracked)
|
||||||
|
assert.False(t, ok, "should reject when at limit")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("other peer unaffected by limit", func(t *testing.T) {
|
||||||
|
tracker := &exposeTracker{}
|
||||||
|
for i := range maxExposesPerPeer {
|
||||||
|
tracker.TrackExposeIfAllowed("peer1", "domain-"+string(rune('a'+i))+".com", "acct1")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, ok := tracker.TrackExposeIfAllowed("peer2", "a.com", "acct1")
|
||||||
|
assert.True(t, ok, "other peer should still be within limit")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUntrackExpose(t *testing.T) {
|
||||||
|
tracker := &exposeTracker{}
|
||||||
|
|
||||||
|
tracker.TrackExposeIfAllowed("peer1", "a.com", "acct1")
|
||||||
|
assert.Equal(t, 1, tracker.CountPeerExposes("peer1"))
|
||||||
|
|
||||||
|
tracker.UntrackExpose("peer1", "a.com")
|
||||||
|
assert.Equal(t, 0, tracker.CountPeerExposes("peer1"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCountPeerExposes(t *testing.T) {
|
||||||
|
tracker := &exposeTracker{}
|
||||||
|
|
||||||
|
assert.Equal(t, 0, tracker.CountPeerExposes("peer1"))
|
||||||
|
|
||||||
|
tracker.TrackExposeIfAllowed("peer1", "a.com", "acct1")
|
||||||
|
tracker.TrackExposeIfAllowed("peer1", "b.com", "acct1")
|
||||||
|
tracker.TrackExposeIfAllowed("peer2", "a.com", "acct1")
|
||||||
|
|
||||||
|
assert.Equal(t, 2, tracker.CountPeerExposes("peer1"), "peer1 should have 2 exposes")
|
||||||
|
assert.Equal(t, 1, tracker.CountPeerExposes("peer2"), "peer2 should have 1 expose")
|
||||||
|
assert.Equal(t, 0, tracker.CountPeerExposes("peer3"), "peer3 should have 0 exposes")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMaxExposesPerPeer(t *testing.T) {
|
||||||
|
tracker := &exposeTracker{}
|
||||||
|
assert.Equal(t, maxExposesPerPeer, tracker.MaxExposesPerPeer())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRenewTrackedExpose(t *testing.T) {
|
||||||
|
tracker := &exposeTracker{}
|
||||||
|
|
||||||
|
found := tracker.RenewTrackedExpose("peer1", "a.com")
|
||||||
|
assert.False(t, found, "should not find untracked expose")
|
||||||
|
|
||||||
|
tracker.TrackExposeIfAllowed("peer1", "a.com", "acct1")
|
||||||
|
|
||||||
|
found = tracker.RenewTrackedExpose("peer1", "a.com")
|
||||||
|
assert.True(t, found, "should find tracked expose")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRenewTrackedExpose_RejectsExpiring(t *testing.T) {
|
||||||
|
tracker := &exposeTracker{}
|
||||||
|
tracker.TrackExposeIfAllowed("peer1", "a.com", "acct1")
|
||||||
|
|
||||||
|
// Simulate reaper marking the expose as expiring
|
||||||
|
key := exposeKey("peer1", "a.com")
|
||||||
|
val, _ := tracker.activeExposes.Load(key)
|
||||||
|
expose := val.(*trackedExpose)
|
||||||
|
expose.mu.Lock()
|
||||||
|
expose.expiring = true
|
||||||
|
expose.mu.Unlock()
|
||||||
|
|
||||||
|
found := tracker.RenewTrackedExpose("peer1", "a.com")
|
||||||
|
assert.False(t, found, "should reject renewal when expiring")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReapExpiredExposes(t *testing.T) {
|
||||||
|
mgr, _ := setupIntegrationTest(t)
|
||||||
|
tracker := mgr.exposeTracker
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
resp, err := mgr.CreateServiceFromPeer(ctx, testAccountID, testPeerID, &rpservice.ExposeServiceRequest{
|
||||||
|
Port: 8080,
|
||||||
|
Protocol: "http",
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Manually expire the tracked entry
|
||||||
|
key := exposeKey(testPeerID, resp.Domain)
|
||||||
|
val, _ := tracker.activeExposes.Load(key)
|
||||||
|
expose := val.(*trackedExpose)
|
||||||
|
expose.mu.Lock()
|
||||||
|
expose.lastRenewed = time.Now().Add(-2 * exposeTTL)
|
||||||
|
expose.mu.Unlock()
|
||||||
|
|
||||||
|
// Add an active (non-expired) tracking entry
|
||||||
|
tracker.activeExposes.Store(exposeKey("peer1", "active.com"), &trackedExpose{
|
||||||
|
domain: "active.com",
|
||||||
|
accountID: testAccountID,
|
||||||
|
peerID: "peer1",
|
||||||
|
lastRenewed: time.Now(),
|
||||||
|
})
|
||||||
|
|
||||||
|
tracker.reapExpiredExposes()
|
||||||
|
|
||||||
|
_, exists := tracker.activeExposes.Load(key)
|
||||||
|
assert.False(t, exists, "expired expose should be removed")
|
||||||
|
|
||||||
|
_, exists = tracker.activeExposes.Load(exposeKey("peer1", "active.com"))
|
||||||
|
assert.True(t, exists, "active expose should remain")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReapExpiredExposes_SetsExpiringFlag(t *testing.T) {
|
||||||
|
mgr, _ := setupIntegrationTest(t)
|
||||||
|
tracker := mgr.exposeTracker
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
resp, err := mgr.CreateServiceFromPeer(ctx, testAccountID, testPeerID, &rpservice.ExposeServiceRequest{
|
||||||
|
Port: 8080,
|
||||||
|
Protocol: "http",
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
key := exposeKey(testPeerID, resp.Domain)
|
||||||
|
val, _ := tracker.activeExposes.Load(key)
|
||||||
|
expose := val.(*trackedExpose)
|
||||||
|
|
||||||
|
// Expire it
|
||||||
|
expose.mu.Lock()
|
||||||
|
expose.lastRenewed = time.Now().Add(-2 * exposeTTL)
|
||||||
|
expose.mu.Unlock()
|
||||||
|
|
||||||
|
// Renew should succeed before reaping
|
||||||
|
assert.True(t, tracker.RenewTrackedExpose(testPeerID, resp.Domain), "renew should succeed before reaper runs")
|
||||||
|
|
||||||
|
// Re-expire and reap
|
||||||
|
expose.mu.Lock()
|
||||||
|
expose.lastRenewed = time.Now().Add(-2 * exposeTTL)
|
||||||
|
expose.mu.Unlock()
|
||||||
|
|
||||||
|
tracker.reapExpiredExposes()
|
||||||
|
|
||||||
|
// Entry is deleted, renew returns false
|
||||||
|
assert.False(t, tracker.RenewTrackedExpose(testPeerID, resp.Domain), "renew should fail after reap")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConcurrentTrackAndCount(t *testing.T) {
|
||||||
|
mgr, _ := setupIntegrationTest(t)
|
||||||
|
tracker := mgr.exposeTracker
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
for i := range 5 {
|
||||||
|
_, err := mgr.CreateServiceFromPeer(ctx, testAccountID, testPeerID, &rpservice.ExposeServiceRequest{
|
||||||
|
Port: 8080 + i,
|
||||||
|
Protocol: "http",
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Manually expire all tracked entries
|
||||||
|
tracker.activeExposes.Range(func(_, val any) bool {
|
||||||
|
expose := val.(*trackedExpose)
|
||||||
|
expose.mu.Lock()
|
||||||
|
expose.lastRenewed = time.Now().Add(-2 * exposeTTL)
|
||||||
|
expose.mu.Unlock()
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(2)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
tracker.reapExpiredExposes()
|
||||||
|
}()
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
tracker.CountPeerExposes(testPeerID)
|
||||||
|
}()
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
assert.Equal(t, 0, tracker.CountPeerExposes(testPeerID), "all expired exposes should be reaped")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTrackedExposeMutexProtectsLastRenewed(t *testing.T) {
|
||||||
|
expose := &trackedExpose{
|
||||||
|
lastRenewed: time.Now().Add(-1 * time.Hour),
|
||||||
|
}
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(2)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
for range 100 {
|
||||||
|
expose.mu.Lock()
|
||||||
|
expose.lastRenewed = time.Now()
|
||||||
|
expose.mu.Unlock()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
for range 100 {
|
||||||
|
expose.mu.Lock()
|
||||||
|
_ = time.Since(expose.lastRenewed)
|
||||||
|
expose.mu.Unlock()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
expose.mu.Lock()
|
||||||
|
require.False(t, expose.lastRenewed.IsZero(), "lastRenewed should not be zero after concurrent access")
|
||||||
|
expose.mu.Unlock()
|
||||||
|
}
|
||||||
@@ -3,10 +3,14 @@ package manager
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/rand/v2"
|
||||||
|
"slices"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/management/internals/modules/reverseproxy/service"
|
"github.com/netbirdio/netbird/management/internals/modules/reverseproxy/service"
|
||||||
"github.com/netbirdio/netbird/management/internals/modules/reverseproxy/sessionkey"
|
"github.com/netbirdio/netbird/management/internals/modules/reverseproxy/sessionkey"
|
||||||
"github.com/netbirdio/netbird/management/server/account"
|
"github.com/netbirdio/netbird/management/server/account"
|
||||||
@@ -23,6 +27,7 @@ const unknownHostPlaceholder = "unknown"
|
|||||||
// ClusterDeriver derives the proxy cluster from a domain.
|
// ClusterDeriver derives the proxy cluster from a domain.
|
||||||
type ClusterDeriver interface {
|
type ClusterDeriver interface {
|
||||||
DeriveClusterFromDomain(ctx context.Context, accountID, domain string) (string, error)
|
DeriveClusterFromDomain(ctx context.Context, accountID, domain string) (string, error)
|
||||||
|
GetClusterDomains() []string
|
||||||
}
|
}
|
||||||
|
|
||||||
type Manager struct {
|
type Manager struct {
|
||||||
@@ -31,17 +36,25 @@ type Manager struct {
|
|||||||
permissionsManager permissions.Manager
|
permissionsManager permissions.Manager
|
||||||
proxyController service.ProxyController
|
proxyController service.ProxyController
|
||||||
clusterDeriver ClusterDeriver
|
clusterDeriver ClusterDeriver
|
||||||
|
exposeTracker *exposeTracker
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewManager creates a new service manager.
|
// NewManager creates a new service manager.
|
||||||
func NewManager(store store.Store, accountManager account.Manager, permissionsManager permissions.Manager, proxyController service.ProxyController, clusterDeriver ClusterDeriver) *Manager {
|
func NewManager(store store.Store, accountManager account.Manager, permissionsManager permissions.Manager, proxyController service.ProxyController, clusterDeriver ClusterDeriver) *Manager {
|
||||||
return &Manager{
|
mgr := &Manager{
|
||||||
store: store,
|
store: store,
|
||||||
accountManager: accountManager,
|
accountManager: accountManager,
|
||||||
permissionsManager: permissionsManager,
|
permissionsManager: permissionsManager,
|
||||||
proxyController: proxyController,
|
proxyController: proxyController,
|
||||||
clusterDeriver: clusterDeriver,
|
clusterDeriver: clusterDeriver,
|
||||||
}
|
}
|
||||||
|
mgr.exposeTracker = &exposeTracker{manager: mgr}
|
||||||
|
return mgr
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartExposeReaper delegates to the expose tracker.
|
||||||
|
func (m *Manager) StartExposeReaper(ctx context.Context) {
|
||||||
|
m.exposeTracker.StartExposeReaper(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) GetAllServices(ctx context.Context, accountID, userID string) ([]*service.Service, error) {
|
func (m *Manager) GetAllServices(ctx context.Context, accountID, userID string) ([]*service.Service, error) {
|
||||||
@@ -394,6 +407,10 @@ func (m *Manager) DeleteService(ctx context.Context, accountID, userID, serviceI
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if s.Source == service.SourceEphemeral {
|
||||||
|
m.exposeTracker.UntrackExpose(s.SourcePeer, s.Domain)
|
||||||
|
}
|
||||||
|
|
||||||
m.accountManager.StoreEvent(ctx, userID, serviceID, accountID, activity.ServiceDeleted, s.EventMeta())
|
m.accountManager.StoreEvent(ctx, userID, serviceID, accountID, activity.ServiceDeleted, s.EventMeta())
|
||||||
|
|
||||||
m.proxyController.SendServiceUpdateToCluster(ctx, accountID, s.ToProtoMapping(service.Delete, "", m.proxyController.GetOIDCValidationConfig()), s.ProxyCluster)
|
m.proxyController.SendServiceUpdateToCluster(ctx, accountID, s.ToProtoMapping(service.Delete, "", m.proxyController.GetOIDCValidationConfig()), s.ProxyCluster)
|
||||||
@@ -435,6 +452,9 @@ func (m *Manager) DeleteAllServices(ctx context.Context, accountID, userID strin
|
|||||||
oidcCfg := m.proxyController.GetOIDCValidationConfig()
|
oidcCfg := m.proxyController.GetOIDCValidationConfig()
|
||||||
|
|
||||||
for _, svc := range services {
|
for _, svc := range services {
|
||||||
|
if svc.Source == service.SourceEphemeral {
|
||||||
|
m.exposeTracker.UntrackExpose(svc.SourcePeer, svc.Domain)
|
||||||
|
}
|
||||||
m.accountManager.StoreEvent(ctx, userID, svc.ID, accountID, activity.ServiceDeleted, svc.EventMeta())
|
m.accountManager.StoreEvent(ctx, userID, svc.ID, accountID, activity.ServiceDeleted, svc.EventMeta())
|
||||||
m.proxyController.SendServiceUpdateToCluster(ctx, accountID, svc.ToProtoMapping(service.Delete, "", oidcCfg), svc.ProxyCluster)
|
m.proxyController.SendServiceUpdateToCluster(ctx, accountID, svc.ToProtoMapping(service.Delete, "", oidcCfg), svc.ProxyCluster)
|
||||||
}
|
}
|
||||||
@@ -453,7 +473,8 @@ func (m *Manager) SetCertificateIssuedAt(ctx context.Context, accountID, service
|
|||||||
return fmt.Errorf("failed to get service: %w", err)
|
return fmt.Errorf("failed to get service: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
service.Meta.CertificateIssuedAt = time.Now()
|
now := time.Now()
|
||||||
|
service.Meta.CertificateIssuedAt = &now
|
||||||
|
|
||||||
if err = transaction.UpdateService(ctx, service); err != nil {
|
if err = transaction.UpdateService(ctx, service); err != nil {
|
||||||
return fmt.Errorf("failed to update service certificate timestamp: %w", err)
|
return fmt.Errorf("failed to update service certificate timestamp: %w", err)
|
||||||
@@ -577,3 +598,258 @@ func (m *Manager) GetServiceIDByTargetID(ctx context.Context, accountID string,
|
|||||||
|
|
||||||
return target.ServiceID, nil
|
return target.ServiceID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// validateExposePermission checks whether the peer is allowed to use the expose feature.
|
||||||
|
// It verifies the account has peer expose enabled and that the peer belongs to an allowed group.
|
||||||
|
func (m *Manager) validateExposePermission(ctx context.Context, accountID, peerID string) error {
|
||||||
|
settings, err := m.store.GetAccountSettings(ctx, store.LockingStrengthNone, accountID)
|
||||||
|
if err != nil {
|
||||||
|
log.WithContext(ctx).Errorf("failed to get account settings: %v", err)
|
||||||
|
return status.Errorf(status.Internal, "get account settings: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !settings.PeerExposeEnabled {
|
||||||
|
return status.Errorf(status.PermissionDenied, "peer expose is not enabled for this account")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(settings.PeerExposeGroups) == 0 {
|
||||||
|
return status.Errorf(status.PermissionDenied, "no group is set for peer expose")
|
||||||
|
}
|
||||||
|
|
||||||
|
peerGroupIDs, err := m.store.GetPeerGroupIDs(ctx, store.LockingStrengthNone, accountID, peerID)
|
||||||
|
if err != nil {
|
||||||
|
log.WithContext(ctx).Errorf("failed to get peer group IDs: %v", err)
|
||||||
|
return status.Errorf(status.Internal, "get peer groups: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, pg := range peerGroupIDs {
|
||||||
|
if slices.Contains(settings.PeerExposeGroups, pg) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return status.Errorf(status.PermissionDenied, "peer is not in an allowed expose group")
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateServiceFromPeer creates a service initiated by a peer expose request.
|
||||||
|
// It validates the request, checks expose permissions, enforces the per-peer limit,
|
||||||
|
// creates the service, and tracks it for TTL-based reaping.
|
||||||
|
func (m *Manager) CreateServiceFromPeer(ctx context.Context, accountID, peerID string, req *service.ExposeServiceRequest) (*service.ExposeServiceResponse, error) {
|
||||||
|
if err := req.Validate(); err != nil {
|
||||||
|
return nil, status.Errorf(status.InvalidArgument, "validate expose request: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := m.validateExposePermission(ctx, accountID, peerID); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
serviceName, err := service.GenerateExposeName(req.NamePrefix)
|
||||||
|
if err != nil {
|
||||||
|
return nil, status.Errorf(status.InvalidArgument, "generate service name: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
svc := req.ToService(accountID, peerID, serviceName)
|
||||||
|
svc.Source = service.SourceEphemeral
|
||||||
|
|
||||||
|
if svc.Domain == "" {
|
||||||
|
domain, err := m.buildRandomDomain(svc.Name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("build random domain for service %s: %w", svc.Name, err)
|
||||||
|
}
|
||||||
|
svc.Domain = domain
|
||||||
|
}
|
||||||
|
|
||||||
|
if svc.Auth.BearerAuth != nil && svc.Auth.BearerAuth.Enabled {
|
||||||
|
groupIDs, err := m.getGroupIDsFromNames(ctx, accountID, svc.Auth.BearerAuth.DistributionGroups)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("get group ids for service %s: %w", svc.Name, err)
|
||||||
|
}
|
||||||
|
svc.Auth.BearerAuth.DistributionGroups = groupIDs
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := m.initializeServiceForCreate(ctx, accountID, svc); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
peer, err := m.store.GetPeerByID(ctx, store.LockingStrengthNone, accountID, peerID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
svc.Meta.LastRenewedAt = &now
|
||||||
|
svc.SourcePeer = peerID
|
||||||
|
|
||||||
|
if err := m.persistNewService(ctx, accountID, svc); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
alreadyTracked, allowed := m.exposeTracker.TrackExposeIfAllowed(peerID, svc.Domain, accountID)
|
||||||
|
if alreadyTracked {
|
||||||
|
if err := m.deleteServiceFromPeer(ctx, accountID, peerID, svc.Domain, false); err != nil {
|
||||||
|
log.WithContext(ctx).Debugf("failed to delete duplicate expose service for domain %s: %v", svc.Domain, err)
|
||||||
|
}
|
||||||
|
return nil, status.Errorf(status.AlreadyExists, "peer already has an active expose session for this domain")
|
||||||
|
}
|
||||||
|
if !allowed {
|
||||||
|
if err := m.deleteServiceFromPeer(ctx, accountID, peerID, svc.Domain, false); err != nil {
|
||||||
|
log.WithContext(ctx).Debugf("failed to delete service after limit exceeded for domain %s: %v", svc.Domain, err)
|
||||||
|
}
|
||||||
|
return nil, status.Errorf(status.PreconditionFailed, "peer has reached the maximum number of active expose sessions (%d)", maxExposesPerPeer)
|
||||||
|
}
|
||||||
|
|
||||||
|
meta := addPeerInfoToEventMeta(svc.EventMeta(), peer)
|
||||||
|
m.accountManager.StoreEvent(ctx, peerID, svc.ID, accountID, activity.PeerServiceExposed, meta)
|
||||||
|
|
||||||
|
if err := m.replaceHostByLookup(ctx, accountID, svc); err != nil {
|
||||||
|
return nil, fmt.Errorf("replace host by lookup for service %s: %w", svc.ID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
m.proxyController.SendServiceUpdateToCluster(ctx, accountID, svc.ToProtoMapping(service.Create, "", m.proxyController.GetOIDCValidationConfig()), svc.ProxyCluster)
|
||||||
|
m.accountManager.UpdateAccountPeers(ctx, accountID)
|
||||||
|
|
||||||
|
return &service.ExposeServiceResponse{
|
||||||
|
ServiceName: svc.Name,
|
||||||
|
ServiceURL: "https://" + svc.Domain,
|
||||||
|
Domain: svc.Domain,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) getGroupIDsFromNames(ctx context.Context, accountID string, groupNames []string) ([]string, error) {
|
||||||
|
if len(groupNames) == 0 {
|
||||||
|
return []string{}, fmt.Errorf("no group names provided")
|
||||||
|
}
|
||||||
|
groupIDs := make([]string, 0, len(groupNames))
|
||||||
|
for _, groupName := range groupNames {
|
||||||
|
g, err := m.accountManager.GetGroupByName(ctx, groupName, accountID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get group by name %s: %w", groupName, err)
|
||||||
|
}
|
||||||
|
groupIDs = append(groupIDs, g.ID)
|
||||||
|
}
|
||||||
|
return groupIDs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) buildRandomDomain(name string) (string, error) {
|
||||||
|
if m.clusterDeriver == nil {
|
||||||
|
return "", fmt.Errorf("unable to get random domain")
|
||||||
|
}
|
||||||
|
clusterDomains := m.clusterDeriver.GetClusterDomains()
|
||||||
|
if len(clusterDomains) == 0 {
|
||||||
|
return "", fmt.Errorf("no cluster domains found for service %s", name)
|
||||||
|
}
|
||||||
|
index := rand.IntN(len(clusterDomains))
|
||||||
|
domain := name + "." + clusterDomains[index]
|
||||||
|
return domain, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RenewServiceFromPeer renews the in-memory TTL tracker for the peer's expose session.
|
||||||
|
// Returns an error if the expose is not actively tracked.
|
||||||
|
func (m *Manager) RenewServiceFromPeer(_ context.Context, _, peerID, domain string) error {
|
||||||
|
if !m.exposeTracker.RenewTrackedExpose(peerID, domain) {
|
||||||
|
return status.Errorf(status.NotFound, "no active expose session for domain %s", domain)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// StopServiceFromPeer stops a peer's active expose session by untracking and deleting the service.
|
||||||
|
func (m *Manager) StopServiceFromPeer(ctx context.Context, accountID, peerID, domain string) error {
|
||||||
|
if err := m.deleteServiceFromPeer(ctx, accountID, peerID, domain, false); err != nil {
|
||||||
|
log.WithContext(ctx).Errorf("failed to delete peer-exposed service for domain %s: %v", domain, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !m.exposeTracker.StopTrackedExpose(peerID, domain) {
|
||||||
|
log.WithContext(ctx).Warnf("expose tracker entry for domain %s already removed; service was deleted", domain)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// deleteServiceFromPeer deletes a peer-initiated service identified by domain.
|
||||||
|
// When expired is true, the activity is recorded as PeerServiceExposeExpired instead of PeerServiceUnexposed.
|
||||||
|
func (m *Manager) deleteServiceFromPeer(ctx context.Context, accountID, peerID, domain string, expired bool) error {
|
||||||
|
svc, err := m.lookupPeerService(ctx, accountID, peerID, domain)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
activityCode := activity.PeerServiceUnexposed
|
||||||
|
if expired {
|
||||||
|
activityCode = activity.PeerServiceExposeExpired
|
||||||
|
}
|
||||||
|
return m.deletePeerService(ctx, accountID, peerID, svc.ID, activityCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// lookupPeerService finds a peer-initiated service by domain and validates ownership.
|
||||||
|
func (m *Manager) lookupPeerService(ctx context.Context, accountID, peerID, domain string) (*service.Service, error) {
|
||||||
|
svc, err := m.store.GetServiceByDomain(ctx, accountID, domain)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if svc.Source != service.SourceEphemeral {
|
||||||
|
return nil, status.Errorf(status.PermissionDenied, "cannot operate on API-created service via peer expose")
|
||||||
|
}
|
||||||
|
|
||||||
|
if svc.SourcePeer != peerID {
|
||||||
|
return nil, status.Errorf(status.PermissionDenied, "cannot operate on service exposed by another peer")
|
||||||
|
}
|
||||||
|
|
||||||
|
return svc, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) deletePeerService(ctx context.Context, accountID, peerID, serviceID string, activityCode activity.Activity) error {
|
||||||
|
var svc *service.Service
|
||||||
|
err := m.store.ExecuteInTransaction(ctx, func(transaction store.Store) error {
|
||||||
|
var err error
|
||||||
|
svc, err = transaction.GetServiceByID(ctx, store.LockingStrengthUpdate, accountID, serviceID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if svc.Source != service.SourceEphemeral {
|
||||||
|
return status.Errorf(status.PermissionDenied, "cannot delete API-created service via peer expose")
|
||||||
|
}
|
||||||
|
|
||||||
|
if svc.SourcePeer != peerID {
|
||||||
|
return status.Errorf(status.PermissionDenied, "cannot delete service exposed by another peer")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = transaction.DeleteService(ctx, accountID, serviceID); err != nil {
|
||||||
|
return fmt.Errorf("delete service: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
peer, err := m.store.GetPeerByID(ctx, store.LockingStrengthNone, accountID, peerID)
|
||||||
|
if err != nil {
|
||||||
|
log.WithContext(ctx).Debugf("failed to get peer %s for event metadata: %v", peerID, err)
|
||||||
|
peer = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
meta := addPeerInfoToEventMeta(svc.EventMeta(), peer)
|
||||||
|
|
||||||
|
m.accountManager.StoreEvent(ctx, peerID, serviceID, accountID, activityCode, meta)
|
||||||
|
|
||||||
|
m.proxyController.SendServiceUpdateToCluster(ctx, accountID, svc.ToProtoMapping(service.Delete, "", m.proxyController.GetOIDCValidationConfig()), svc.ProxyCluster)
|
||||||
|
|
||||||
|
m.accountManager.UpdateAccountPeers(ctx, accountID)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func addPeerInfoToEventMeta(meta map[string]any, peer *nbpeer.Peer) map[string]any {
|
||||||
|
if peer == nil {
|
||||||
|
return meta
|
||||||
|
}
|
||||||
|
meta["peer_name"] = peer.Name
|
||||||
|
if peer.IP != nil {
|
||||||
|
meta["peer_ip"] = peer.IP.String()
|
||||||
|
}
|
||||||
|
return meta
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package manager
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"net"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -11,7 +12,13 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
rpservice "github.com/netbirdio/netbird/management/internals/modules/reverseproxy/service"
|
rpservice "github.com/netbirdio/netbird/management/internals/modules/reverseproxy/service"
|
||||||
|
nbgrpc "github.com/netbirdio/netbird/management/internals/shared/grpc"
|
||||||
|
"github.com/netbirdio/netbird/management/server/activity"
|
||||||
|
"github.com/netbirdio/netbird/management/server/mock_server"
|
||||||
|
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
||||||
|
"github.com/netbirdio/netbird/management/server/permissions"
|
||||||
"github.com/netbirdio/netbird/management/server/store"
|
"github.com/netbirdio/netbird/management/server/store"
|
||||||
|
"github.com/netbirdio/netbird/management/server/types"
|
||||||
"github.com/netbirdio/netbird/shared/management/status"
|
"github.com/netbirdio/netbird/shared/management/status"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -356,7 +363,7 @@ func TestPreserveServiceMetadata(t *testing.T) {
|
|||||||
|
|
||||||
existing := &rpservice.Service{
|
existing := &rpservice.Service{
|
||||||
Meta: rpservice.ServiceMeta{
|
Meta: rpservice.ServiceMeta{
|
||||||
CertificateIssuedAt: time.Now(),
|
CertificateIssuedAt: func() *time.Time { t := time.Now(); return &t }(),
|
||||||
Status: "active",
|
Status: "active",
|
||||||
},
|
},
|
||||||
SessionPrivateKey: "private-key",
|
SessionPrivateKey: "private-key",
|
||||||
@@ -373,3 +380,715 @@ func TestPreserveServiceMetadata(t *testing.T) {
|
|||||||
assert.Equal(t, existing.SessionPrivateKey, updated.SessionPrivateKey)
|
assert.Equal(t, existing.SessionPrivateKey, updated.SessionPrivateKey)
|
||||||
assert.Equal(t, existing.SessionPublicKey, updated.SessionPublicKey)
|
assert.Equal(t, existing.SessionPublicKey, updated.SessionPublicKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDeletePeerService_SourcePeerValidation(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
accountID := "test-account"
|
||||||
|
ownerPeerID := "peer-owner"
|
||||||
|
otherPeerID := "peer-other"
|
||||||
|
serviceID := "service-123"
|
||||||
|
|
||||||
|
testPeer := &nbpeer.Peer{
|
||||||
|
ID: ownerPeerID,
|
||||||
|
Name: "test-peer",
|
||||||
|
IP: net.ParseIP("100.64.0.1"),
|
||||||
|
}
|
||||||
|
|
||||||
|
newEphemeralService := func() *rpservice.Service {
|
||||||
|
return &rpservice.Service{
|
||||||
|
ID: serviceID,
|
||||||
|
AccountID: accountID,
|
||||||
|
Name: "test-service",
|
||||||
|
Domain: "test.example.com",
|
||||||
|
Source: rpservice.SourceEphemeral,
|
||||||
|
SourcePeer: ownerPeerID,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
newPermanentService := func() *rpservice.Service {
|
||||||
|
return &rpservice.Service{
|
||||||
|
ID: serviceID,
|
||||||
|
AccountID: accountID,
|
||||||
|
Name: "api-service",
|
||||||
|
Domain: "api.example.com",
|
||||||
|
Source: rpservice.SourcePermanent,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
newProxyServer := func(t *testing.T) *nbgrpc.ProxyServiceServer {
|
||||||
|
t.Helper()
|
||||||
|
tokenStore, err := nbgrpc.NewOneTimeTokenStore(context.Background(), 1*time.Hour, 10*time.Minute, 100)
|
||||||
|
require.NoError(t, err)
|
||||||
|
srv := nbgrpc.NewProxyServiceServer(nil, tokenStore, nbgrpc.ProxyOIDCConfig{}, nil, nil, nil)
|
||||||
|
t.Cleanup(srv.Close)
|
||||||
|
return srv
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("owner peer can delete own service", func(t *testing.T) {
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
defer ctrl.Finish()
|
||||||
|
|
||||||
|
var storedActivity activity.Activity
|
||||||
|
mockStore := store.NewMockStore(ctrl)
|
||||||
|
mockAccountMgr := &mock_server.MockAccountManager{
|
||||||
|
StoreEventFunc: func(_ context.Context, _, _, _ string, activityID activity.ActivityDescriber, _ map[string]any) {
|
||||||
|
storedActivity = activityID.(activity.Activity)
|
||||||
|
},
|
||||||
|
UpdateAccountPeersFunc: func(_ context.Context, _ string) {},
|
||||||
|
}
|
||||||
|
|
||||||
|
mockStore.EXPECT().
|
||||||
|
ExecuteInTransaction(ctx, gomock.Any()).
|
||||||
|
DoAndReturn(func(ctx context.Context, fn func(store.Store) error) error {
|
||||||
|
txMock := store.NewMockStore(ctrl)
|
||||||
|
txMock.EXPECT().
|
||||||
|
GetServiceByID(ctx, store.LockingStrengthUpdate, accountID, serviceID).
|
||||||
|
Return(newEphemeralService(), nil)
|
||||||
|
txMock.EXPECT().
|
||||||
|
DeleteService(ctx, accountID, serviceID).
|
||||||
|
Return(nil)
|
||||||
|
return fn(txMock)
|
||||||
|
})
|
||||||
|
mockStore.EXPECT().
|
||||||
|
GetPeerByID(ctx, store.LockingStrengthNone, accountID, ownerPeerID).
|
||||||
|
Return(testPeer, nil)
|
||||||
|
|
||||||
|
mgr := &Manager{
|
||||||
|
store: mockStore,
|
||||||
|
accountManager: mockAccountMgr,
|
||||||
|
proxyController: NewGRPCProxyController(newProxyServer(t)),
|
||||||
|
}
|
||||||
|
|
||||||
|
err := mgr.deletePeerService(ctx, accountID, ownerPeerID, serviceID, activity.PeerServiceUnexposed)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, activity.PeerServiceUnexposed, storedActivity, "should store unexposed activity")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("different peer cannot delete service", func(t *testing.T) {
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
defer ctrl.Finish()
|
||||||
|
|
||||||
|
mockStore := store.NewMockStore(ctrl)
|
||||||
|
|
||||||
|
mockStore.EXPECT().
|
||||||
|
ExecuteInTransaction(ctx, gomock.Any()).
|
||||||
|
DoAndReturn(func(ctx context.Context, fn func(store.Store) error) error {
|
||||||
|
txMock := store.NewMockStore(ctrl)
|
||||||
|
txMock.EXPECT().
|
||||||
|
GetServiceByID(ctx, store.LockingStrengthUpdate, accountID, serviceID).
|
||||||
|
Return(newEphemeralService(), nil)
|
||||||
|
return fn(txMock)
|
||||||
|
})
|
||||||
|
|
||||||
|
mgr := &Manager{
|
||||||
|
store: mockStore,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := mgr.deletePeerService(ctx, accountID, otherPeerID, serviceID, activity.PeerServiceUnexposed)
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
sErr, ok := status.FromError(err)
|
||||||
|
require.True(t, ok, "should be a status error")
|
||||||
|
assert.Equal(t, status.PermissionDenied, sErr.Type(), "should be permission denied")
|
||||||
|
assert.Contains(t, err.Error(), "another peer")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("cannot delete API-created service", func(t *testing.T) {
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
defer ctrl.Finish()
|
||||||
|
|
||||||
|
mockStore := store.NewMockStore(ctrl)
|
||||||
|
|
||||||
|
mockStore.EXPECT().
|
||||||
|
ExecuteInTransaction(ctx, gomock.Any()).
|
||||||
|
DoAndReturn(func(ctx context.Context, fn func(store.Store) error) error {
|
||||||
|
txMock := store.NewMockStore(ctrl)
|
||||||
|
txMock.EXPECT().
|
||||||
|
GetServiceByID(ctx, store.LockingStrengthUpdate, accountID, serviceID).
|
||||||
|
Return(newPermanentService(), nil)
|
||||||
|
return fn(txMock)
|
||||||
|
})
|
||||||
|
|
||||||
|
mgr := &Manager{
|
||||||
|
store: mockStore,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := mgr.deletePeerService(ctx, accountID, ownerPeerID, serviceID, activity.PeerServiceUnexposed)
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
sErr, ok := status.FromError(err)
|
||||||
|
require.True(t, ok, "should be a status error")
|
||||||
|
assert.Equal(t, status.PermissionDenied, sErr.Type(), "should be permission denied")
|
||||||
|
assert.Contains(t, err.Error(), "API-created")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("expire uses correct activity code", func(t *testing.T) {
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
defer ctrl.Finish()
|
||||||
|
|
||||||
|
var storedActivity activity.Activity
|
||||||
|
mockStore := store.NewMockStore(ctrl)
|
||||||
|
mockAccountMgr := &mock_server.MockAccountManager{
|
||||||
|
StoreEventFunc: func(_ context.Context, _, _, _ string, activityID activity.ActivityDescriber, _ map[string]any) {
|
||||||
|
storedActivity = activityID.(activity.Activity)
|
||||||
|
},
|
||||||
|
UpdateAccountPeersFunc: func(_ context.Context, _ string) {},
|
||||||
|
}
|
||||||
|
|
||||||
|
mockStore.EXPECT().
|
||||||
|
ExecuteInTransaction(ctx, gomock.Any()).
|
||||||
|
DoAndReturn(func(ctx context.Context, fn func(store.Store) error) error {
|
||||||
|
txMock := store.NewMockStore(ctrl)
|
||||||
|
txMock.EXPECT().
|
||||||
|
GetServiceByID(ctx, store.LockingStrengthUpdate, accountID, serviceID).
|
||||||
|
Return(newEphemeralService(), nil)
|
||||||
|
txMock.EXPECT().
|
||||||
|
DeleteService(ctx, accountID, serviceID).
|
||||||
|
Return(nil)
|
||||||
|
return fn(txMock)
|
||||||
|
})
|
||||||
|
mockStore.EXPECT().
|
||||||
|
GetPeerByID(ctx, store.LockingStrengthNone, accountID, ownerPeerID).
|
||||||
|
Return(testPeer, nil)
|
||||||
|
|
||||||
|
mgr := &Manager{
|
||||||
|
store: mockStore,
|
||||||
|
accountManager: mockAccountMgr,
|
||||||
|
proxyController: NewGRPCProxyController(newProxyServer(t)),
|
||||||
|
}
|
||||||
|
|
||||||
|
err := mgr.deletePeerService(ctx, accountID, ownerPeerID, serviceID, activity.PeerServiceExposeExpired)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, activity.PeerServiceExposeExpired, storedActivity, "should store expired activity")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("event meta includes peer info", func(t *testing.T) {
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
defer ctrl.Finish()
|
||||||
|
|
||||||
|
var storedMeta map[string]any
|
||||||
|
mockStore := store.NewMockStore(ctrl)
|
||||||
|
mockAccountMgr := &mock_server.MockAccountManager{
|
||||||
|
StoreEventFunc: func(_ context.Context, _, _, _ string, _ activity.ActivityDescriber, meta map[string]any) {
|
||||||
|
storedMeta = meta
|
||||||
|
},
|
||||||
|
UpdateAccountPeersFunc: func(_ context.Context, _ string) {},
|
||||||
|
}
|
||||||
|
|
||||||
|
mockStore.EXPECT().
|
||||||
|
ExecuteInTransaction(ctx, gomock.Any()).
|
||||||
|
DoAndReturn(func(ctx context.Context, fn func(store.Store) error) error {
|
||||||
|
txMock := store.NewMockStore(ctrl)
|
||||||
|
txMock.EXPECT().
|
||||||
|
GetServiceByID(ctx, store.LockingStrengthUpdate, accountID, serviceID).
|
||||||
|
Return(newEphemeralService(), nil)
|
||||||
|
txMock.EXPECT().
|
||||||
|
DeleteService(ctx, accountID, serviceID).
|
||||||
|
Return(nil)
|
||||||
|
return fn(txMock)
|
||||||
|
})
|
||||||
|
mockStore.EXPECT().
|
||||||
|
GetPeerByID(ctx, store.LockingStrengthNone, accountID, ownerPeerID).
|
||||||
|
Return(testPeer, nil)
|
||||||
|
|
||||||
|
mgr := &Manager{
|
||||||
|
store: mockStore,
|
||||||
|
accountManager: mockAccountMgr,
|
||||||
|
proxyController: NewGRPCProxyController(newProxyServer(t)),
|
||||||
|
}
|
||||||
|
|
||||||
|
err := mgr.deletePeerService(ctx, accountID, ownerPeerID, serviceID, activity.PeerServiceUnexposed)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, storedMeta)
|
||||||
|
assert.Equal(t, "test-peer", storedMeta["peer_name"], "meta should contain peer name")
|
||||||
|
assert.Equal(t, "100.64.0.1", storedMeta["peer_ip"], "meta should contain peer IP")
|
||||||
|
assert.Equal(t, "test-service", storedMeta["name"], "meta should contain service name")
|
||||||
|
assert.Equal(t, "test.example.com", storedMeta["domain"], "meta should contain service domain")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// testClusterDeriver is a minimal ClusterDeriver that returns a fixed domain list.
|
||||||
|
type testClusterDeriver struct {
|
||||||
|
domains []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *testClusterDeriver) DeriveClusterFromDomain(_ context.Context, _, domain string) (string, error) {
|
||||||
|
return "test-cluster", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *testClusterDeriver) GetClusterDomains() []string {
|
||||||
|
return d.domains
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
testAccountID = "test-account"
|
||||||
|
testPeerID = "test-peer-1"
|
||||||
|
testGroupID = "test-group-1"
|
||||||
|
testUserID = "test-user"
|
||||||
|
)
|
||||||
|
|
||||||
|
// setupIntegrationTest creates a real SQLite store with seeded test data for integration tests.
|
||||||
|
func setupIntegrationTest(t *testing.T) (*Manager, store.Store) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
testStore, cleanup, err := store.NewTestStoreFromSQL(ctx, "", t.TempDir())
|
||||||
|
require.NoError(t, err)
|
||||||
|
t.Cleanup(cleanup)
|
||||||
|
|
||||||
|
err = testStore.SaveAccount(ctx, &types.Account{
|
||||||
|
Id: testAccountID,
|
||||||
|
CreatedBy: testUserID,
|
||||||
|
Settings: &types.Settings{
|
||||||
|
PeerExposeEnabled: true,
|
||||||
|
PeerExposeGroups: []string{testGroupID},
|
||||||
|
},
|
||||||
|
Users: map[string]*types.User{
|
||||||
|
testUserID: {
|
||||||
|
Id: testUserID,
|
||||||
|
AccountID: testAccountID,
|
||||||
|
Role: types.UserRoleAdmin,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Peers: map[string]*nbpeer.Peer{
|
||||||
|
testPeerID: {
|
||||||
|
ID: testPeerID,
|
||||||
|
AccountID: testAccountID,
|
||||||
|
Key: "test-key",
|
||||||
|
DNSLabel: "test-peer",
|
||||||
|
Name: "test-peer",
|
||||||
|
IP: net.ParseIP("100.64.0.1"),
|
||||||
|
Status: &nbpeer.PeerStatus{Connected: true, LastSeen: time.Now()},
|
||||||
|
Meta: nbpeer.PeerSystemMeta{Hostname: "test-peer"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Groups: map[string]*types.Group{
|
||||||
|
testGroupID: {
|
||||||
|
ID: testGroupID,
|
||||||
|
AccountID: testAccountID,
|
||||||
|
Name: "Expose Group",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = testStore.AddPeerToGroup(ctx, testAccountID, testPeerID, testGroupID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
permsMgr := permissions.NewManager(testStore)
|
||||||
|
|
||||||
|
accountMgr := &mock_server.MockAccountManager{
|
||||||
|
StoreEventFunc: func(_ context.Context, _, _, _ string, _ activity.ActivityDescriber, _ map[string]any) {},
|
||||||
|
UpdateAccountPeersFunc: func(_ context.Context, _ string) {},
|
||||||
|
GetGroupByNameFunc: func(ctx context.Context, accountID, groupName string) (*types.Group, error) {
|
||||||
|
return testStore.GetGroupByName(ctx, store.LockingStrengthNone, groupName, accountID)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
tokenStore, err := nbgrpc.NewOneTimeTokenStore(ctx, 1*time.Hour, 10*time.Minute, 100)
|
||||||
|
require.NoError(t, err)
|
||||||
|
proxySrv := nbgrpc.NewProxyServiceServer(nil, tokenStore, nbgrpc.ProxyOIDCConfig{}, nil, nil, nil)
|
||||||
|
t.Cleanup(proxySrv.Close)
|
||||||
|
|
||||||
|
mgr := &Manager{
|
||||||
|
store: testStore,
|
||||||
|
accountManager: accountMgr,
|
||||||
|
permissionsManager: permsMgr,
|
||||||
|
proxyController: NewGRPCProxyController(proxySrv),
|
||||||
|
clusterDeriver: &testClusterDeriver{
|
||||||
|
domains: []string{"test.netbird.io"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
mgr.exposeTracker = &exposeTracker{manager: mgr}
|
||||||
|
|
||||||
|
return mgr, testStore
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_validateExposePermission(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
t.Run("allowed when peer is in expose group", func(t *testing.T) {
|
||||||
|
mgr, _ := setupIntegrationTest(t)
|
||||||
|
err := mgr.validateExposePermission(ctx, testAccountID, testPeerID)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("denied when peer is not in expose group", func(t *testing.T) {
|
||||||
|
mgr, testStore := setupIntegrationTest(t)
|
||||||
|
|
||||||
|
// Add a peer that is NOT in the expose group
|
||||||
|
otherPeerID := "other-peer"
|
||||||
|
err := testStore.AddPeerToAccount(ctx, &nbpeer.Peer{
|
||||||
|
ID: otherPeerID,
|
||||||
|
AccountID: testAccountID,
|
||||||
|
Key: "other-key",
|
||||||
|
DNSLabel: "other-peer",
|
||||||
|
Name: "other-peer",
|
||||||
|
IP: net.ParseIP("100.64.0.2"),
|
||||||
|
Status: &nbpeer.PeerStatus{LastSeen: time.Now()},
|
||||||
|
Meta: nbpeer.PeerSystemMeta{Hostname: "other-peer"},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = mgr.validateExposePermission(ctx, testAccountID, otherPeerID)
|
||||||
|
require.Error(t, err)
|
||||||
|
assert.Contains(t, err.Error(), "not in an allowed expose group")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("denied when expose is disabled", func(t *testing.T) {
|
||||||
|
mgr, testStore := setupIntegrationTest(t)
|
||||||
|
|
||||||
|
// Disable peer expose
|
||||||
|
s, err := testStore.GetAccountSettings(ctx, store.LockingStrengthNone, testAccountID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
s.PeerExposeEnabled = false
|
||||||
|
err = testStore.SaveAccountSettings(ctx, testAccountID, s)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = mgr.validateExposePermission(ctx, testAccountID, testPeerID)
|
||||||
|
require.Error(t, err)
|
||||||
|
assert.Contains(t, err.Error(), "not enabled")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("disallowed when no groups configured", func(t *testing.T) {
|
||||||
|
mgr, testStore := setupIntegrationTest(t)
|
||||||
|
|
||||||
|
// Enable expose with empty groups — no groups configured means no peer is allowed
|
||||||
|
s, err := testStore.GetAccountSettings(ctx, store.LockingStrengthNone, testAccountID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
s.PeerExposeGroups = []string{}
|
||||||
|
err = testStore.SaveAccountSettings(ctx, testAccountID, s)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = mgr.validateExposePermission(ctx, testAccountID, testPeerID)
|
||||||
|
assert.Error(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("error when store returns error", func(t *testing.T) {
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
mockStore := store.NewMockStore(ctrl)
|
||||||
|
mockStore.EXPECT().GetAccountSettings(gomock.Any(), gomock.Any(), testAccountID).Return(nil, errors.New("store error"))
|
||||||
|
mgr := &Manager{store: mockStore}
|
||||||
|
err := mgr.validateExposePermission(ctx, testAccountID, testPeerID)
|
||||||
|
require.Error(t, err)
|
||||||
|
assert.Contains(t, err.Error(), "get account settings")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateServiceFromPeer(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
t.Run("creates service with random domain", func(t *testing.T) {
|
||||||
|
mgr, testStore := setupIntegrationTest(t)
|
||||||
|
|
||||||
|
req := &rpservice.ExposeServiceRequest{
|
||||||
|
Port: 8080,
|
||||||
|
Protocol: "http",
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := mgr.CreateServiceFromPeer(ctx, testAccountID, testPeerID, req)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.NotEmpty(t, resp.ServiceName, "service name should be generated")
|
||||||
|
assert.Contains(t, resp.Domain, "test.netbird.io", "domain should use cluster domain")
|
||||||
|
assert.NotEmpty(t, resp.ServiceURL, "service URL should be set")
|
||||||
|
|
||||||
|
// Verify service is persisted in store
|
||||||
|
persisted, err := testStore.GetServiceByDomain(ctx, testAccountID, resp.Domain)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, resp.Domain, persisted.Domain)
|
||||||
|
assert.Equal(t, rpservice.SourceEphemeral, persisted.Source, "source should be ephemeral")
|
||||||
|
assert.Equal(t, testPeerID, persisted.SourcePeer, "source peer should be set")
|
||||||
|
assert.NotNil(t, persisted.Meta.LastRenewedAt, "last renewed should be set")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("creates service with custom domain", func(t *testing.T) {
|
||||||
|
mgr, _ := setupIntegrationTest(t)
|
||||||
|
|
||||||
|
req := &rpservice.ExposeServiceRequest{
|
||||||
|
Port: 80,
|
||||||
|
Protocol: "http",
|
||||||
|
Domain: "example.com",
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := mgr.CreateServiceFromPeer(ctx, testAccountID, testPeerID, req)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Contains(t, resp.Domain, "example.com", "should use the provided domain")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("validates expose permission internally", func(t *testing.T) {
|
||||||
|
mgr, testStore := setupIntegrationTest(t)
|
||||||
|
|
||||||
|
// Disable peer expose
|
||||||
|
s, err := testStore.GetAccountSettings(ctx, store.LockingStrengthNone, testAccountID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
s.PeerExposeEnabled = false
|
||||||
|
err = testStore.SaveAccountSettings(ctx, testAccountID, s)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
req := &rpservice.ExposeServiceRequest{
|
||||||
|
Port: 8080,
|
||||||
|
Protocol: "http",
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = mgr.CreateServiceFromPeer(ctx, testAccountID, testPeerID, req)
|
||||||
|
require.Error(t, err)
|
||||||
|
assert.Contains(t, err.Error(), "not enabled")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("validates request fields", func(t *testing.T) {
|
||||||
|
mgr, _ := setupIntegrationTest(t)
|
||||||
|
|
||||||
|
req := &rpservice.ExposeServiceRequest{
|
||||||
|
Port: 0,
|
||||||
|
Protocol: "http",
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := mgr.CreateServiceFromPeer(ctx, testAccountID, testPeerID, req)
|
||||||
|
require.Error(t, err)
|
||||||
|
assert.Contains(t, err.Error(), "port")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExposeServiceRequestValidate(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
req rpservice.ExposeServiceRequest
|
||||||
|
wantErr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "valid http request",
|
||||||
|
req: rpservice.ExposeServiceRequest{Port: 8080, Protocol: "http"},
|
||||||
|
wantErr: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid https request with pin",
|
||||||
|
req: rpservice.ExposeServiceRequest{Port: 443, Protocol: "https", Pin: "123456"},
|
||||||
|
wantErr: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "port zero rejected",
|
||||||
|
req: rpservice.ExposeServiceRequest{Port: 0, Protocol: "http"},
|
||||||
|
wantErr: "port must be between 1 and 65535",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "negative port rejected",
|
||||||
|
req: rpservice.ExposeServiceRequest{Port: -1, Protocol: "http"},
|
||||||
|
wantErr: "port must be between 1 and 65535",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "port above 65535 rejected",
|
||||||
|
req: rpservice.ExposeServiceRequest{Port: 65536, Protocol: "http"},
|
||||||
|
wantErr: "port must be between 1 and 65535",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unsupported protocol",
|
||||||
|
req: rpservice.ExposeServiceRequest{Port: 80, Protocol: "tcp"},
|
||||||
|
wantErr: "unsupported protocol",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid pin format",
|
||||||
|
req: rpservice.ExposeServiceRequest{Port: 80, Protocol: "http", Pin: "abc"},
|
||||||
|
wantErr: "invalid pin",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "pin too short",
|
||||||
|
req: rpservice.ExposeServiceRequest{Port: 80, Protocol: "http", Pin: "12345"},
|
||||||
|
wantErr: "invalid pin",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid 6-digit pin",
|
||||||
|
req: rpservice.ExposeServiceRequest{Port: 80, Protocol: "http", Pin: "000000"},
|
||||||
|
wantErr: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty user group name",
|
||||||
|
req: rpservice.ExposeServiceRequest{Port: 80, Protocol: "http", UserGroups: []string{"valid", ""}},
|
||||||
|
wantErr: "user group name cannot be empty",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid name prefix",
|
||||||
|
req: rpservice.ExposeServiceRequest{Port: 80, Protocol: "http", NamePrefix: "INVALID"},
|
||||||
|
wantErr: "invalid name prefix",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid name prefix",
|
||||||
|
req: rpservice.ExposeServiceRequest{Port: 80, Protocol: "http", NamePrefix: "my-service"},
|
||||||
|
wantErr: "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
err := tt.req.Validate()
|
||||||
|
if tt.wantErr == "" {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
} else {
|
||||||
|
require.Error(t, err)
|
||||||
|
assert.Contains(t, err.Error(), tt.wantErr)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("nil receiver", func(t *testing.T) {
|
||||||
|
var req *rpservice.ExposeServiceRequest
|
||||||
|
err := req.Validate()
|
||||||
|
require.Error(t, err)
|
||||||
|
assert.Contains(t, err.Error(), "request cannot be nil")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDeleteServiceFromPeer_ByDomain(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
t.Run("deletes service by domain", func(t *testing.T) {
|
||||||
|
mgr, testStore := setupIntegrationTest(t)
|
||||||
|
|
||||||
|
// First create a service
|
||||||
|
req := &rpservice.ExposeServiceRequest{
|
||||||
|
Port: 8080,
|
||||||
|
Protocol: "http",
|
||||||
|
}
|
||||||
|
resp, err := mgr.CreateServiceFromPeer(ctx, testAccountID, testPeerID, req)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Delete by domain using unexported method
|
||||||
|
err = mgr.deleteServiceFromPeer(ctx, testAccountID, testPeerID, resp.Domain, false)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Verify service is deleted
|
||||||
|
_, err = testStore.GetServiceByDomain(ctx, testAccountID, resp.Domain)
|
||||||
|
require.Error(t, err, "service should be deleted")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("expire uses correct activity", func(t *testing.T) {
|
||||||
|
mgr, _ := setupIntegrationTest(t)
|
||||||
|
|
||||||
|
req := &rpservice.ExposeServiceRequest{
|
||||||
|
Port: 8080,
|
||||||
|
Protocol: "http",
|
||||||
|
}
|
||||||
|
resp, err := mgr.CreateServiceFromPeer(ctx, testAccountID, testPeerID, req)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = mgr.deleteServiceFromPeer(ctx, testAccountID, testPeerID, resp.Domain, true)
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStopServiceFromPeer(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
t.Run("stops service by domain", func(t *testing.T) {
|
||||||
|
mgr, testStore := setupIntegrationTest(t)
|
||||||
|
|
||||||
|
req := &rpservice.ExposeServiceRequest{
|
||||||
|
Port: 8080,
|
||||||
|
Protocol: "http",
|
||||||
|
}
|
||||||
|
resp, err := mgr.CreateServiceFromPeer(ctx, testAccountID, testPeerID, req)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = mgr.StopServiceFromPeer(ctx, testAccountID, testPeerID, resp.Domain)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
_, err = testStore.GetServiceByDomain(ctx, testAccountID, resp.Domain)
|
||||||
|
require.Error(t, err, "service should be deleted")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDeleteService_UntracksEphemeralExpose(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
mgr, _ := setupIntegrationTest(t)
|
||||||
|
|
||||||
|
resp, err := mgr.CreateServiceFromPeer(ctx, testAccountID, testPeerID, &rpservice.ExposeServiceRequest{
|
||||||
|
Port: 8080,
|
||||||
|
Protocol: "http",
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, 1, mgr.exposeTracker.CountPeerExposes(testPeerID), "expose should be tracked after create")
|
||||||
|
|
||||||
|
// Look up the service by domain to get its store ID
|
||||||
|
svc, err := mgr.store.GetServiceByDomain(ctx, testAccountID, resp.Domain)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Delete via the API path (user-initiated)
|
||||||
|
err = mgr.DeleteService(ctx, testAccountID, testUserID, svc.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, 0, mgr.exposeTracker.CountPeerExposes(testPeerID), "expose should be untracked after API delete")
|
||||||
|
|
||||||
|
// A new expose should succeed (not blocked by stale tracking)
|
||||||
|
_, err = mgr.CreateServiceFromPeer(ctx, testAccountID, testPeerID, &rpservice.ExposeServiceRequest{
|
||||||
|
Port: 9090,
|
||||||
|
Protocol: "http",
|
||||||
|
})
|
||||||
|
assert.NoError(t, err, "new expose should succeed after API delete cleared tracking")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDeleteAllServices_UntracksEphemeralExposes(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
mgr, _ := setupIntegrationTest(t)
|
||||||
|
|
||||||
|
for i := range 3 {
|
||||||
|
_, err := mgr.CreateServiceFromPeer(ctx, testAccountID, testPeerID, &rpservice.ExposeServiceRequest{
|
||||||
|
Port: 8080 + i,
|
||||||
|
Protocol: "http",
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, 3, mgr.exposeTracker.CountPeerExposes(testPeerID), "all exposes should be tracked")
|
||||||
|
|
||||||
|
err := mgr.DeleteAllServices(ctx, testAccountID, testUserID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, 0, mgr.exposeTracker.CountPeerExposes(testPeerID), "all exposes should be untracked after DeleteAllServices")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRenewServiceFromPeer(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
t.Run("renews tracked expose", func(t *testing.T) {
|
||||||
|
mgr, _ := setupIntegrationTest(t)
|
||||||
|
|
||||||
|
resp, err := mgr.CreateServiceFromPeer(ctx, testAccountID, testPeerID, &rpservice.ExposeServiceRequest{
|
||||||
|
Port: 8080,
|
||||||
|
Protocol: "http",
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = mgr.RenewServiceFromPeer(ctx, testAccountID, testPeerID, resp.Domain)
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("fails for untracked domain", func(t *testing.T) {
|
||||||
|
mgr, _ := setupIntegrationTest(t)
|
||||||
|
err := mgr.RenewServiceFromPeer(ctx, testAccountID, testPeerID, "nonexistent.com")
|
||||||
|
require.Error(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetGroupIDsFromNames(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
t.Run("resolves group names to IDs", func(t *testing.T) {
|
||||||
|
mgr, _ := setupIntegrationTest(t)
|
||||||
|
ids, err := mgr.getGroupIDsFromNames(ctx, testAccountID, []string{"Expose Group"})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, ids, 1, "should return exactly one group ID")
|
||||||
|
assert.Equal(t, testGroupID, ids[0])
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("returns error for unknown group", func(t *testing.T) {
|
||||||
|
mgr, _ := setupIntegrationTest(t)
|
||||||
|
_, err := mgr.getGroupIDsFromNames(ctx, testAccountID, []string{"nonexistent"})
|
||||||
|
require.Error(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("returns error for empty group list", func(t *testing.T) {
|
||||||
|
mgr, _ := setupIntegrationTest(t)
|
||||||
|
_, err := mgr.getGroupIDsFromNames(ctx, testAccountID, []string{})
|
||||||
|
require.Error(t, err)
|
||||||
|
assert.Contains(t, err.Error(), "no group names provided")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/rand"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/big"
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -40,6 +43,9 @@ const (
|
|||||||
TargetTypeHost = "host"
|
TargetTypeHost = "host"
|
||||||
TargetTypeDomain = "domain"
|
TargetTypeDomain = "domain"
|
||||||
TargetTypeSubnet = "subnet"
|
TargetTypeSubnet = "subnet"
|
||||||
|
|
||||||
|
SourcePermanent = "permanent"
|
||||||
|
SourceEphemeral = "ephemeral"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Target struct {
|
type Target struct {
|
||||||
@@ -114,8 +120,9 @@ type OIDCValidationConfig struct {
|
|||||||
|
|
||||||
type ServiceMeta struct {
|
type ServiceMeta struct {
|
||||||
CreatedAt time.Time
|
CreatedAt time.Time
|
||||||
CertificateIssuedAt time.Time
|
CertificateIssuedAt *time.Time
|
||||||
Status string
|
Status string
|
||||||
|
LastRenewedAt *time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
type Service struct {
|
type Service struct {
|
||||||
@@ -132,6 +139,8 @@ type Service struct {
|
|||||||
Meta ServiceMeta `gorm:"embedded;embeddedPrefix:meta_"`
|
Meta ServiceMeta `gorm:"embedded;embeddedPrefix:meta_"`
|
||||||
SessionPrivateKey string `gorm:"column:session_private_key"`
|
SessionPrivateKey string `gorm:"column:session_private_key"`
|
||||||
SessionPublicKey string `gorm:"column:session_public_key"`
|
SessionPublicKey string `gorm:"column:session_public_key"`
|
||||||
|
Source string `gorm:"default:'permanent'"`
|
||||||
|
SourcePeer string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewService(accountID, name, domain, proxyCluster string, targets []*Target, enabled bool) *Service {
|
func NewService(accountID, name, domain, proxyCluster string, targets []*Target, enabled bool) *Service {
|
||||||
@@ -207,8 +216,8 @@ func (s *Service) ToAPIResponse() *api.Service {
|
|||||||
Status: api.ServiceMetaStatus(s.Meta.Status),
|
Status: api.ServiceMetaStatus(s.Meta.Status),
|
||||||
}
|
}
|
||||||
|
|
||||||
if !s.Meta.CertificateIssuedAt.IsZero() {
|
if s.Meta.CertificateIssuedAt != nil {
|
||||||
meta.CertificateIssuedAt = &s.Meta.CertificateIssuedAt
|
meta.CertificateIssuedAt = s.Meta.CertificateIssuedAt
|
||||||
}
|
}
|
||||||
|
|
||||||
resp := &api.Service{
|
resp := &api.Service{
|
||||||
@@ -403,7 +412,11 @@ func (s *Service) Validate() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) EventMeta() map[string]any {
|
func (s *Service) EventMeta() map[string]any {
|
||||||
return map[string]any{"name": s.Name, "domain": s.Domain, "proxy_cluster": s.ProxyCluster}
|
return map[string]any{"name": s.Name, "domain": s.Domain, "proxy_cluster": s.ProxyCluster, "source": s.Source, "auth": s.isAuthEnabled()}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) isAuthEnabled() bool {
|
||||||
|
return s.Auth.PasswordAuth != nil || s.Auth.PinAuth != nil || s.Auth.BearerAuth != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) Copy() *Service {
|
func (s *Service) Copy() *Service {
|
||||||
@@ -427,6 +440,8 @@ func (s *Service) Copy() *Service {
|
|||||||
Meta: s.Meta,
|
Meta: s.Meta,
|
||||||
SessionPrivateKey: s.SessionPrivateKey,
|
SessionPrivateKey: s.SessionPrivateKey,
|
||||||
SessionPublicKey: s.SessionPublicKey,
|
SessionPublicKey: s.SessionPublicKey,
|
||||||
|
Source: s.Source,
|
||||||
|
SourcePeer: s.SourcePeer,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -461,3 +476,140 @@ func (s *Service) DecryptSensitiveData(enc *crypt.FieldEncrypt) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var pinRegexp = regexp.MustCompile(`^\d{6}$`)
|
||||||
|
|
||||||
|
const alphanumCharset = "abcdefghijklmnopqrstuvwxyz0123456789"
|
||||||
|
|
||||||
|
var validNamePrefix = regexp.MustCompile(`^[a-z0-9]([a-z0-9-]{0,30}[a-z0-9])?$`)
|
||||||
|
|
||||||
|
// ExposeServiceRequest contains the parameters for creating a peer-initiated expose service.
|
||||||
|
type ExposeServiceRequest struct {
|
||||||
|
NamePrefix string
|
||||||
|
Port int
|
||||||
|
Protocol string
|
||||||
|
Domain string
|
||||||
|
Pin string
|
||||||
|
Password string
|
||||||
|
UserGroups []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate checks all fields of the expose request.
|
||||||
|
func (r *ExposeServiceRequest) Validate() error {
|
||||||
|
if r == nil {
|
||||||
|
return errors.New("request cannot be nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.Port < 1 || r.Port > 65535 {
|
||||||
|
return fmt.Errorf("port must be between 1 and 65535, got %d", r.Port)
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.Protocol != "http" && r.Protocol != "https" {
|
||||||
|
return fmt.Errorf("unsupported protocol %q: must be http or https", r.Protocol)
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.Pin != "" && !pinRegexp.MatchString(r.Pin) {
|
||||||
|
return errors.New("invalid pin: must be exactly 6 digits")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, g := range r.UserGroups {
|
||||||
|
if g == "" {
|
||||||
|
return errors.New("user group name cannot be empty")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.NamePrefix != "" && !validNamePrefix.MatchString(r.NamePrefix) {
|
||||||
|
return fmt.Errorf("invalid name prefix %q: must be lowercase alphanumeric with optional hyphens, 1-32 characters", r.NamePrefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToService builds a Service from the expose request.
|
||||||
|
func (r *ExposeServiceRequest) ToService(accountID, peerID, serviceName string) *Service {
|
||||||
|
service := &Service{
|
||||||
|
AccountID: accountID,
|
||||||
|
Name: serviceName,
|
||||||
|
Enabled: true,
|
||||||
|
Targets: []*Target{
|
||||||
|
{
|
||||||
|
AccountID: accountID,
|
||||||
|
Port: r.Port,
|
||||||
|
Protocol: r.Protocol,
|
||||||
|
TargetId: peerID,
|
||||||
|
TargetType: TargetTypePeer,
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.Domain != "" {
|
||||||
|
service.Domain = serviceName + "." + r.Domain
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.Pin != "" {
|
||||||
|
service.Auth.PinAuth = &PINAuthConfig{
|
||||||
|
Enabled: true,
|
||||||
|
Pin: r.Pin,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.Password != "" {
|
||||||
|
service.Auth.PasswordAuth = &PasswordAuthConfig{
|
||||||
|
Enabled: true,
|
||||||
|
Password: r.Password,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(r.UserGroups) > 0 {
|
||||||
|
service.Auth.BearerAuth = &BearerAuthConfig{
|
||||||
|
Enabled: true,
|
||||||
|
DistributionGroups: r.UserGroups,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return service
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExposeServiceResponse contains the result of a successful peer expose creation.
|
||||||
|
type ExposeServiceResponse struct {
|
||||||
|
ServiceName string
|
||||||
|
ServiceURL string
|
||||||
|
Domain string
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateExposeName generates a random service name for peer-exposed services.
|
||||||
|
// The prefix, if provided, must be a valid DNS label component (lowercase alphanumeric and hyphens).
|
||||||
|
func GenerateExposeName(prefix string) (string, error) {
|
||||||
|
if prefix != "" && !validNamePrefix.MatchString(prefix) {
|
||||||
|
return "", fmt.Errorf("invalid name prefix %q: must be lowercase alphanumeric with optional hyphens, 1-32 characters", prefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
suffixLen := 12
|
||||||
|
if prefix != "" {
|
||||||
|
suffixLen = 4
|
||||||
|
}
|
||||||
|
|
||||||
|
suffix, err := randomAlphanumeric(suffixLen)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("generate random name: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if prefix == "" {
|
||||||
|
return suffix, nil
|
||||||
|
}
|
||||||
|
return prefix + "-" + suffix, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func randomAlphanumeric(n int) (string, error) {
|
||||||
|
result := make([]byte, n)
|
||||||
|
charsetLen := big.NewInt(int64(len(alphanumCharset)))
|
||||||
|
for i := range result {
|
||||||
|
idx, err := rand.Int(rand.Reader, charsetLen)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
result[i] = alphanumCharset[idx.Int64()]
|
||||||
|
}
|
||||||
|
return string(result), nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -403,3 +403,146 @@ func TestAuthConfig_ClearSecrets(t *testing.T) {
|
|||||||
t.Errorf("PIN not cleared, got: %s", config.PinAuth.Pin)
|
t.Errorf("PIN not cleared, got: %s", config.PinAuth.Pin)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGenerateExposeName(t *testing.T) {
|
||||||
|
t.Run("no prefix generates 12-char name", func(t *testing.T) {
|
||||||
|
name, err := GenerateExposeName("")
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Len(t, name, 12)
|
||||||
|
assert.Regexp(t, `^[a-z0-9]+$`, name)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with prefix generates prefix-XXXX", func(t *testing.T) {
|
||||||
|
name, err := GenerateExposeName("myapp")
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.True(t, strings.HasPrefix(name, "myapp-"), "name should start with prefix")
|
||||||
|
suffix := strings.TrimPrefix(name, "myapp-")
|
||||||
|
assert.Len(t, suffix, 4, "suffix should be 4 chars")
|
||||||
|
assert.Regexp(t, `^[a-z0-9]+$`, suffix)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("unique names", func(t *testing.T) {
|
||||||
|
names := make(map[string]bool)
|
||||||
|
for i := 0; i < 50; i++ {
|
||||||
|
name, err := GenerateExposeName("")
|
||||||
|
require.NoError(t, err)
|
||||||
|
names[name] = true
|
||||||
|
}
|
||||||
|
assert.Greater(t, len(names), 45, "should generate mostly unique names")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("valid prefixes", func(t *testing.T) {
|
||||||
|
validPrefixes := []string{"a", "ab", "a1", "my-app", "web-server-01", "a-b"}
|
||||||
|
for _, prefix := range validPrefixes {
|
||||||
|
name, err := GenerateExposeName(prefix)
|
||||||
|
assert.NoError(t, err, "prefix %q should be valid", prefix)
|
||||||
|
assert.True(t, strings.HasPrefix(name, prefix+"-"), "name should start with %q-", prefix)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid prefixes", func(t *testing.T) {
|
||||||
|
invalidPrefixes := []string{
|
||||||
|
"-starts-with-dash",
|
||||||
|
"ends-with-dash-",
|
||||||
|
"has.dots",
|
||||||
|
"HAS-UPPER",
|
||||||
|
"has spaces",
|
||||||
|
"has/slash",
|
||||||
|
"a--",
|
||||||
|
}
|
||||||
|
for _, prefix := range invalidPrefixes {
|
||||||
|
_, err := GenerateExposeName(prefix)
|
||||||
|
assert.Error(t, err, "prefix %q should be invalid", prefix)
|
||||||
|
assert.Contains(t, err.Error(), "invalid name prefix")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExposeServiceRequest_ToService(t *testing.T) {
|
||||||
|
t.Run("basic HTTP service", func(t *testing.T) {
|
||||||
|
req := &ExposeServiceRequest{
|
||||||
|
Port: 8080,
|
||||||
|
Protocol: "http",
|
||||||
|
}
|
||||||
|
|
||||||
|
service := req.ToService("account-1", "peer-1", "mysvc")
|
||||||
|
|
||||||
|
assert.Equal(t, "account-1", service.AccountID)
|
||||||
|
assert.Equal(t, "mysvc", service.Name)
|
||||||
|
assert.True(t, service.Enabled)
|
||||||
|
assert.Empty(t, service.Domain, "domain should be empty when not specified")
|
||||||
|
require.Len(t, service.Targets, 1)
|
||||||
|
|
||||||
|
target := service.Targets[0]
|
||||||
|
assert.Equal(t, 8080, target.Port)
|
||||||
|
assert.Equal(t, "http", target.Protocol)
|
||||||
|
assert.Equal(t, "peer-1", target.TargetId)
|
||||||
|
assert.Equal(t, TargetTypePeer, target.TargetType)
|
||||||
|
assert.True(t, target.Enabled)
|
||||||
|
assert.Equal(t, "account-1", target.AccountID)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with custom domain", func(t *testing.T) {
|
||||||
|
req := &ExposeServiceRequest{
|
||||||
|
Port: 3000,
|
||||||
|
Domain: "example.com",
|
||||||
|
}
|
||||||
|
|
||||||
|
service := req.ToService("acc", "peer", "web")
|
||||||
|
assert.Equal(t, "web.example.com", service.Domain)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with PIN auth", func(t *testing.T) {
|
||||||
|
req := &ExposeServiceRequest{
|
||||||
|
Port: 80,
|
||||||
|
Pin: "1234",
|
||||||
|
}
|
||||||
|
|
||||||
|
service := req.ToService("acc", "peer", "svc")
|
||||||
|
require.NotNil(t, service.Auth.PinAuth)
|
||||||
|
assert.True(t, service.Auth.PinAuth.Enabled)
|
||||||
|
assert.Equal(t, "1234", service.Auth.PinAuth.Pin)
|
||||||
|
assert.Nil(t, service.Auth.PasswordAuth)
|
||||||
|
assert.Nil(t, service.Auth.BearerAuth)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with password auth", func(t *testing.T) {
|
||||||
|
req := &ExposeServiceRequest{
|
||||||
|
Port: 80,
|
||||||
|
Password: "secret",
|
||||||
|
}
|
||||||
|
|
||||||
|
service := req.ToService("acc", "peer", "svc")
|
||||||
|
require.NotNil(t, service.Auth.PasswordAuth)
|
||||||
|
assert.True(t, service.Auth.PasswordAuth.Enabled)
|
||||||
|
assert.Equal(t, "secret", service.Auth.PasswordAuth.Password)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with user groups (bearer auth)", func(t *testing.T) {
|
||||||
|
req := &ExposeServiceRequest{
|
||||||
|
Port: 80,
|
||||||
|
UserGroups: []string{"admins", "devs"},
|
||||||
|
}
|
||||||
|
|
||||||
|
service := req.ToService("acc", "peer", "svc")
|
||||||
|
require.NotNil(t, service.Auth.BearerAuth)
|
||||||
|
assert.True(t, service.Auth.BearerAuth.Enabled)
|
||||||
|
assert.Equal(t, []string{"admins", "devs"}, service.Auth.BearerAuth.DistributionGroups)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with all auth types", func(t *testing.T) {
|
||||||
|
req := &ExposeServiceRequest{
|
||||||
|
Port: 443,
|
||||||
|
Domain: "myco.com",
|
||||||
|
Pin: "9999",
|
||||||
|
Password: "pass",
|
||||||
|
UserGroups: []string{"ops"},
|
||||||
|
}
|
||||||
|
|
||||||
|
service := req.ToService("acc", "peer", "full")
|
||||||
|
assert.Equal(t, "full.myco.com", service.Domain)
|
||||||
|
require.NotNil(t, service.Auth.PinAuth)
|
||||||
|
require.NotNil(t, service.Auth.PasswordAuth)
|
||||||
|
require.NotNil(t, service.Auth.BearerAuth)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -152,6 +152,11 @@ func (s *BaseServer) GRPCServer() *grpc.Server {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed to create management server: %v", err)
|
log.Fatalf("failed to create management server: %v", err)
|
||||||
}
|
}
|
||||||
|
serviceMgr := s.ServiceManager()
|
||||||
|
srv.SetReverseProxyManager(serviceMgr)
|
||||||
|
if serviceMgr != nil {
|
||||||
|
serviceMgr.StartExposeReaper(context.Background())
|
||||||
|
}
|
||||||
mgmtProto.RegisterManagementServiceServer(gRPCAPIHandler, srv)
|
mgmtProto.RegisterManagementServiceServer(gRPCAPIHandler, srv)
|
||||||
|
|
||||||
mgmtProto.RegisterProxyServiceServer(gRPCAPIHandler, s.ReverseProxyGRPCServer())
|
mgmtProto.RegisterProxyServiceServer(gRPCAPIHandler, s.ReverseProxyGRPCServer())
|
||||||
|
|||||||
@@ -0,0 +1,192 @@
|
|||||||
|
package grpc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
pb "github.com/golang/protobuf/proto" // nolint
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/encryption"
|
||||||
|
rpservice "github.com/netbirdio/netbird/management/internals/modules/reverseproxy/service"
|
||||||
|
nbContext "github.com/netbirdio/netbird/management/server/context"
|
||||||
|
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
||||||
|
"github.com/netbirdio/netbird/management/server/store"
|
||||||
|
"github.com/netbirdio/netbird/shared/management/proto"
|
||||||
|
internalStatus "github.com/netbirdio/netbird/shared/management/status"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CreateExpose handles a peer request to create a new expose service.
|
||||||
|
func (s *Server) CreateExpose(ctx context.Context, req *proto.EncryptedMessage) (*proto.EncryptedMessage, error) {
|
||||||
|
exposeReq := &proto.ExposeServiceRequest{}
|
||||||
|
peerKey, err := s.parseRequest(ctx, req, exposeReq)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
accountID, peer, err := s.authenticateExposePeer(ctx, peerKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// nolint:staticcheck
|
||||||
|
ctx = context.WithValue(ctx, nbContext.AccountIDKey, accountID)
|
||||||
|
|
||||||
|
reverseProxyMgr := s.getReverseProxyManager()
|
||||||
|
if reverseProxyMgr == nil {
|
||||||
|
return nil, status.Errorf(codes.Internal, "reverse proxy manager not available")
|
||||||
|
}
|
||||||
|
|
||||||
|
created, err := reverseProxyMgr.CreateServiceFromPeer(ctx, accountID, peer.ID, &rpservice.ExposeServiceRequest{
|
||||||
|
NamePrefix: exposeReq.NamePrefix,
|
||||||
|
Port: int(exposeReq.Port),
|
||||||
|
Protocol: exposeProtocolToString(exposeReq.Protocol),
|
||||||
|
Domain: exposeReq.Domain,
|
||||||
|
Pin: exposeReq.Pin,
|
||||||
|
Password: exposeReq.Password,
|
||||||
|
UserGroups: exposeReq.UserGroups,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, mapExposeError(ctx, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.encryptResponse(peerKey, &proto.ExposeServiceResponse{
|
||||||
|
ServiceName: created.ServiceName,
|
||||||
|
ServiceUrl: created.ServiceURL,
|
||||||
|
Domain: created.Domain,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// RenewExpose extends the TTL of an active expose session.
|
||||||
|
func (s *Server) RenewExpose(ctx context.Context, req *proto.EncryptedMessage) (*proto.EncryptedMessage, error) {
|
||||||
|
renewReq := &proto.RenewExposeRequest{}
|
||||||
|
peerKey, err := s.parseRequest(ctx, req, renewReq)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
accountID, peer, err := s.authenticateExposePeer(ctx, peerKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
reverseProxyMgr := s.getReverseProxyManager()
|
||||||
|
if reverseProxyMgr == nil {
|
||||||
|
return nil, status.Errorf(codes.Internal, "reverse proxy manager not available")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := reverseProxyMgr.RenewServiceFromPeer(ctx, accountID, peer.ID, renewReq.Domain); err != nil {
|
||||||
|
return nil, mapExposeError(ctx, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.encryptResponse(peerKey, &proto.RenewExposeResponse{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// StopExpose terminates an active expose session.
|
||||||
|
func (s *Server) StopExpose(ctx context.Context, req *proto.EncryptedMessage) (*proto.EncryptedMessage, error) {
|
||||||
|
stopReq := &proto.StopExposeRequest{}
|
||||||
|
peerKey, err := s.parseRequest(ctx, req, stopReq)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
accountID, peer, err := s.authenticateExposePeer(ctx, peerKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
reverseProxyMgr := s.getReverseProxyManager()
|
||||||
|
if reverseProxyMgr == nil {
|
||||||
|
return nil, status.Errorf(codes.Internal, "reverse proxy manager not available")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := reverseProxyMgr.StopServiceFromPeer(ctx, accountID, peer.ID, stopReq.Domain); err != nil {
|
||||||
|
return nil, mapExposeError(ctx, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.encryptResponse(peerKey, &proto.StopExposeResponse{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func mapExposeError(ctx context.Context, err error) error {
|
||||||
|
s, ok := internalStatus.FromError(err)
|
||||||
|
if !ok {
|
||||||
|
log.WithContext(ctx).Errorf("expose service error: %v", err)
|
||||||
|
return status.Errorf(codes.Internal, "internal error")
|
||||||
|
}
|
||||||
|
|
||||||
|
switch s.Type() {
|
||||||
|
case internalStatus.InvalidArgument:
|
||||||
|
return status.Errorf(codes.InvalidArgument, "%s", s.Message)
|
||||||
|
case internalStatus.PermissionDenied:
|
||||||
|
return status.Errorf(codes.PermissionDenied, "%s", s.Message)
|
||||||
|
case internalStatus.NotFound:
|
||||||
|
return status.Errorf(codes.NotFound, "%s", s.Message)
|
||||||
|
case internalStatus.AlreadyExists:
|
||||||
|
return status.Errorf(codes.AlreadyExists, "%s", s.Message)
|
||||||
|
case internalStatus.PreconditionFailed:
|
||||||
|
return status.Errorf(codes.ResourceExhausted, "%s", s.Message)
|
||||||
|
default:
|
||||||
|
log.WithContext(ctx).Errorf("expose service error: %v", err)
|
||||||
|
return status.Errorf(codes.Internal, "internal error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) encryptResponse(peerKey wgtypes.Key, msg pb.Message) (*proto.EncryptedMessage, error) {
|
||||||
|
wgKey, err := s.secretsManager.GetWGKey()
|
||||||
|
if err != nil {
|
||||||
|
return nil, status.Errorf(codes.Internal, "internal error")
|
||||||
|
}
|
||||||
|
|
||||||
|
encryptedResp, err := encryption.EncryptMessage(peerKey, wgKey, msg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, status.Errorf(codes.Internal, "encrypt response")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &proto.EncryptedMessage{
|
||||||
|
WgPubKey: wgKey.PublicKey().String(),
|
||||||
|
Body: encryptedResp,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) authenticateExposePeer(ctx context.Context, peerKey wgtypes.Key) (string, *nbpeer.Peer, error) {
|
||||||
|
accountID, err := s.accountManager.GetAccountIDForPeerKey(ctx, peerKey.String())
|
||||||
|
if err != nil {
|
||||||
|
if errStatus, ok := internalStatus.FromError(err); ok && errStatus.Type() == internalStatus.NotFound {
|
||||||
|
return "", nil, status.Errorf(codes.PermissionDenied, "peer is not registered")
|
||||||
|
}
|
||||||
|
return "", nil, status.Errorf(codes.Internal, "lookup account for peer")
|
||||||
|
}
|
||||||
|
|
||||||
|
peer, err := s.accountManager.GetStore().GetPeerByPeerPubKey(ctx, store.LockingStrengthNone, peerKey.String())
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, status.Errorf(codes.PermissionDenied, "peer is not registered")
|
||||||
|
}
|
||||||
|
|
||||||
|
return accountID, peer, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) getReverseProxyManager() rpservice.Manager {
|
||||||
|
s.reverseProxyMu.RLock()
|
||||||
|
defer s.reverseProxyMu.RUnlock()
|
||||||
|
return s.reverseProxyManager
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetReverseProxyManager sets the reverse proxy manager on the server.
|
||||||
|
func (s *Server) SetReverseProxyManager(mgr rpservice.Manager) {
|
||||||
|
s.reverseProxyMu.Lock()
|
||||||
|
defer s.reverseProxyMu.Unlock()
|
||||||
|
s.reverseProxyManager = mgr
|
||||||
|
}
|
||||||
|
|
||||||
|
func exposeProtocolToString(p proto.ExposeProtocol) string {
|
||||||
|
switch p {
|
||||||
|
case proto.ExposeProtocol_EXPOSE_HTTP:
|
||||||
|
return "http"
|
||||||
|
case proto.ExposeProtocol_EXPOSE_HTTPS:
|
||||||
|
return "https"
|
||||||
|
default:
|
||||||
|
return "http"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -76,6 +76,20 @@ func (m *mockReverseProxyManager) GetServiceIDByTargetID(_ context.Context, _, _
|
|||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *mockReverseProxyManager) CreateServiceFromPeer(_ context.Context, _, _ string, _ *service.ExposeServiceRequest) (*service.ExposeServiceResponse, error) {
|
||||||
|
return &service.ExposeServiceResponse{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockReverseProxyManager) RenewServiceFromPeer(_ context.Context, _, _, _ string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockReverseProxyManager) StopServiceFromPeer(_ context.Context, _, _, _ string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockReverseProxyManager) StartExposeReaper(_ context.Context) {}
|
||||||
|
|
||||||
type mockUsersManager struct {
|
type mockUsersManager struct {
|
||||||
users map[string]*types.User
|
users map[string]*types.User
|
||||||
err error
|
err error
|
||||||
|
|||||||
@@ -8,12 +8,66 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
rpservice "github.com/netbirdio/netbird/management/internals/modules/reverseproxy/service"
|
||||||
"github.com/netbirdio/netbird/shared/management/proto"
|
"github.com/netbirdio/netbird/shared/management/proto"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type testProxyController struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
clusterProxies map[string]map[string]struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTestProxyController() *testProxyController {
|
||||||
|
return &testProxyController{
|
||||||
|
clusterProxies: make(map[string]map[string]struct{}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *testProxyController) SendServiceUpdateToCluster(_ context.Context, _ string, _ *proto.ProxyMapping, _ string) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *testProxyController) GetOIDCValidationConfig() rpservice.OIDCValidationConfig {
|
||||||
|
return rpservice.OIDCValidationConfig{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *testProxyController) RegisterProxyToCluster(_ context.Context, clusterAddr, proxyID string) error {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
if _, ok := c.clusterProxies[clusterAddr]; !ok {
|
||||||
|
c.clusterProxies[clusterAddr] = make(map[string]struct{})
|
||||||
|
}
|
||||||
|
c.clusterProxies[clusterAddr][proxyID] = struct{}{}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *testProxyController) UnregisterProxyFromCluster(_ context.Context, clusterAddr, proxyID string) error {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
if proxies, ok := c.clusterProxies[clusterAddr]; ok {
|
||||||
|
delete(proxies, proxyID)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *testProxyController) GetProxiesForCluster(clusterAddr string) []string {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
proxies, ok := c.clusterProxies[clusterAddr]
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
result := make([]string, 0, len(proxies))
|
||||||
|
for id := range proxies {
|
||||||
|
result = append(result, id)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
// registerFakeProxy adds a fake proxy connection to the server's internal maps
|
// registerFakeProxy adds a fake proxy connection to the server's internal maps
|
||||||
// and returns the channel where messages will be received.
|
// and returns the channel where messages will be received.
|
||||||
func registerFakeProxy(s *ProxyServiceServer, proxyID, clusterAddr string) chan *proto.GetMappingUpdateResponse {
|
func registerFakeProxy(s *ProxyServiceServer, proxyID, clusterAddr string) chan *proto.GetMappingUpdateResponse {
|
||||||
@@ -46,6 +100,7 @@ func TestSendServiceUpdateToCluster_UniqueTokensPerProxy(t *testing.T) {
|
|||||||
s := &ProxyServiceServer{
|
s := &ProxyServiceServer{
|
||||||
tokenStore: tokenStore,
|
tokenStore: tokenStore,
|
||||||
}
|
}
|
||||||
|
s.SetProxyController(newTestProxyController())
|
||||||
|
|
||||||
const cluster = "proxy.example.com"
|
const cluster = "proxy.example.com"
|
||||||
const numProxies = 3
|
const numProxies = 3
|
||||||
@@ -102,6 +157,7 @@ func TestSendServiceUpdateToCluster_DeleteNoToken(t *testing.T) {
|
|||||||
s := &ProxyServiceServer{
|
s := &ProxyServiceServer{
|
||||||
tokenStore: tokenStore,
|
tokenStore: tokenStore,
|
||||||
}
|
}
|
||||||
|
s.SetProxyController(newTestProxyController())
|
||||||
|
|
||||||
const cluster = "proxy.example.com"
|
const cluster = "proxy.example.com"
|
||||||
ch1 := registerFakeProxy(s, "proxy-a", cluster)
|
ch1 := registerFakeProxy(s, "proxy-a", cluster)
|
||||||
@@ -135,6 +191,7 @@ func TestSendServiceUpdate_UniqueTokensPerProxy(t *testing.T) {
|
|||||||
s := &ProxyServiceServer{
|
s := &ProxyServiceServer{
|
||||||
tokenStore: tokenStore,
|
tokenStore: tokenStore,
|
||||||
}
|
}
|
||||||
|
s.SetProxyController(newTestProxyController())
|
||||||
|
|
||||||
// Register proxies in different clusters (SendServiceUpdate broadcasts to all)
|
// Register proxies in different clusters (SendServiceUpdate broadcasts to all)
|
||||||
ch1 := registerFakeProxy(s, "proxy-a", "cluster-a")
|
ch1 := registerFakeProxy(s, "proxy-a", "cluster-a")
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import (
|
|||||||
"github.com/netbirdio/netbird/shared/management/client/common"
|
"github.com/netbirdio/netbird/shared/management/client/common"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/management/internals/controllers/network_map"
|
"github.com/netbirdio/netbird/management/internals/controllers/network_map"
|
||||||
|
rpservice "github.com/netbirdio/netbird/management/internals/modules/reverseproxy/service"
|
||||||
nbconfig "github.com/netbirdio/netbird/management/internals/server/config"
|
nbconfig "github.com/netbirdio/netbird/management/internals/server/config"
|
||||||
"github.com/netbirdio/netbird/management/server/idp"
|
"github.com/netbirdio/netbird/management/server/idp"
|
||||||
"github.com/netbirdio/netbird/management/server/job"
|
"github.com/netbirdio/netbird/management/server/job"
|
||||||
@@ -80,6 +81,9 @@ type Server struct {
|
|||||||
syncSem atomic.Int32
|
syncSem atomic.Int32
|
||||||
syncLimEnabled bool
|
syncLimEnabled bool
|
||||||
syncLim int32
|
syncLim int32
|
||||||
|
|
||||||
|
reverseProxyManager rpservice.Manager
|
||||||
|
reverseProxyMu sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewServer creates a new Management server
|
// NewServer creates a new Management server
|
||||||
|
|||||||
@@ -303,6 +303,20 @@ func (m *testValidateSessionServiceManager) GetServiceIDByTargetID(_ context.Con
|
|||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *testValidateSessionServiceManager) CreateServiceFromPeer(_ context.Context, _, _ string, _ *service.ExposeServiceRequest) (*service.ExposeServiceResponse, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *testValidateSessionServiceManager) RenewServiceFromPeer(_ context.Context, _, _, _ string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *testValidateSessionServiceManager) StopServiceFromPeer(_ context.Context, _, _, _ string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *testValidateSessionServiceManager) StartExposeReaper(_ context.Context) {}
|
||||||
|
|
||||||
type testValidateSessionProxyManager struct{}
|
type testValidateSessionProxyManager struct{}
|
||||||
|
|
||||||
func (m *testValidateSessionProxyManager) Connect(_ context.Context, _, _, _ string) error {
|
func (m *testValidateSessionProxyManager) Connect(_ context.Context, _, _, _ string) error {
|
||||||
|
|||||||
@@ -376,6 +376,7 @@ func (am *DefaultAccountManager) UpdateAccountSettings(ctx context.Context, acco
|
|||||||
am.handlePeerLoginExpirationSettings(ctx, oldSettings, newSettings, userID, accountID)
|
am.handlePeerLoginExpirationSettings(ctx, oldSettings, newSettings, userID, accountID)
|
||||||
am.handleGroupsPropagationSettings(ctx, oldSettings, newSettings, userID, accountID)
|
am.handleGroupsPropagationSettings(ctx, oldSettings, newSettings, userID, accountID)
|
||||||
am.handleAutoUpdateVersionSettings(ctx, oldSettings, newSettings, userID, accountID)
|
am.handleAutoUpdateVersionSettings(ctx, oldSettings, newSettings, userID, accountID)
|
||||||
|
am.handlePeerExposeSettings(ctx, oldSettings, newSettings, userID, accountID)
|
||||||
if err = am.handleInactivityExpirationSettings(ctx, oldSettings, newSettings, userID, accountID); err != nil {
|
if err = am.handleInactivityExpirationSettings(ctx, oldSettings, newSettings, userID, accountID); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -492,6 +493,21 @@ func (am *DefaultAccountManager) handleAutoUpdateVersionSettings(ctx context.Con
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (am *DefaultAccountManager) handlePeerExposeSettings(ctx context.Context, oldSettings, newSettings *types.Settings, userID, accountID string) {
|
||||||
|
oldEnabled := oldSettings.PeerExposeEnabled
|
||||||
|
newEnabled := newSettings.PeerExposeEnabled
|
||||||
|
|
||||||
|
if oldEnabled == newEnabled {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
event := activity.AccountPeerExposeEnabled
|
||||||
|
if !newEnabled {
|
||||||
|
event = activity.AccountPeerExposeDisabled
|
||||||
|
}
|
||||||
|
am.StoreEvent(ctx, userID, accountID, accountID, event, nil)
|
||||||
|
}
|
||||||
|
|
||||||
func (am *DefaultAccountManager) handleInactivityExpirationSettings(ctx context.Context, oldSettings, newSettings *types.Settings, userID, accountID string) error {
|
func (am *DefaultAccountManager) handleInactivityExpirationSettings(ctx context.Context, oldSettings, newSettings *types.Settings, userID, accountID string) error {
|
||||||
if newSettings.PeerInactivityExpirationEnabled {
|
if newSettings.PeerInactivityExpirationEnabled {
|
||||||
if oldSettings.PeerInactivityExpiration != newSettings.PeerInactivityExpiration {
|
if oldSettings.PeerInactivityExpiration != newSettings.PeerInactivityExpiration {
|
||||||
|
|||||||
@@ -3124,7 +3124,7 @@ func createManager(t testing.TB) (*DefaultAccountManager, *update_channel.PeersU
|
|||||||
}
|
}
|
||||||
|
|
||||||
proxyGrpcServer := nbgrpc.NewProxyServiceServer(nil, nil, nbgrpc.ProxyOIDCConfig{}, peersManager, nil)
|
proxyGrpcServer := nbgrpc.NewProxyServiceServer(nil, nil, nbgrpc.ProxyOIDCConfig{}, peersManager, nil)
|
||||||
manager.SetServiceManager(reverseproxymanager.NewManager(store, manager, permissionsManager, proxyGrpcServer, nil))
|
manager.SetServiceManager(reverseproxymanager.NewManager(store, manager, permissionsManager, settingsMockManager, proxyGrpcServer, nil))
|
||||||
|
|
||||||
return manager, updateManager, nil
|
return manager, updateManager, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -208,6 +208,18 @@ const (
|
|||||||
ServiceUpdated Activity = 109
|
ServiceUpdated Activity = 109
|
||||||
ServiceDeleted Activity = 110
|
ServiceDeleted Activity = 110
|
||||||
|
|
||||||
|
// PeerServiceExposed indicates that a peer exposed a service via the reverse proxy
|
||||||
|
PeerServiceExposed Activity = 111
|
||||||
|
// PeerServiceUnexposed indicates that a peer-exposed service was removed
|
||||||
|
PeerServiceUnexposed Activity = 112
|
||||||
|
// PeerServiceExposeExpired indicates that a peer-exposed service was removed due to TTL expiration
|
||||||
|
PeerServiceExposeExpired Activity = 113
|
||||||
|
|
||||||
|
// AccountPeerExposeEnabled indicates that a user enabled peer expose for the account
|
||||||
|
AccountPeerExposeEnabled Activity = 114
|
||||||
|
// AccountPeerExposeDisabled indicates that a user disabled peer expose for the account
|
||||||
|
AccountPeerExposeDisabled Activity = 115
|
||||||
|
|
||||||
AccountDeleted Activity = 99999
|
AccountDeleted Activity = 99999
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -345,6 +357,13 @@ var activityMap = map[Activity]Code{
|
|||||||
ServiceCreated: {"Service created", "service.create"},
|
ServiceCreated: {"Service created", "service.create"},
|
||||||
ServiceUpdated: {"Service updated", "service.update"},
|
ServiceUpdated: {"Service updated", "service.update"},
|
||||||
ServiceDeleted: {"Service deleted", "service.delete"},
|
ServiceDeleted: {"Service deleted", "service.delete"},
|
||||||
|
|
||||||
|
PeerServiceExposed: {"Peer exposed service", "service.peer.expose"},
|
||||||
|
PeerServiceUnexposed: {"Peer unexposed service", "service.peer.unexpose"},
|
||||||
|
PeerServiceExposeExpired: {"Peer exposed service expired", "service.peer.expose.expire"},
|
||||||
|
|
||||||
|
AccountPeerExposeEnabled: {"Account peer expose enabled", "account.setting.peer.expose.enable"},
|
||||||
|
AccountPeerExposeDisabled: {"Account peer expose disabled", "account.setting.peer.expose.disable"},
|
||||||
}
|
}
|
||||||
|
|
||||||
// StringCode returns a string code of the activity
|
// StringCode returns a string code of the activity
|
||||||
|
|||||||
@@ -168,6 +168,10 @@ func (h *handler) getAllAccounts(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *handler) updateAccountRequestSettings(req api.PutApiAccountsAccountIdJSONRequestBody) (*types.Settings, error) {
|
func (h *handler) updateAccountRequestSettings(req api.PutApiAccountsAccountIdJSONRequestBody) (*types.Settings, error) {
|
||||||
|
if req.Settings.PeerExposeEnabled && len(req.Settings.PeerExposeGroups) == 0 {
|
||||||
|
return nil, status.Errorf(status.InvalidArgument, "peer expose requires at least one group")
|
||||||
|
}
|
||||||
|
|
||||||
returnSettings := &types.Settings{
|
returnSettings := &types.Settings{
|
||||||
PeerLoginExpirationEnabled: req.Settings.PeerLoginExpirationEnabled,
|
PeerLoginExpirationEnabled: req.Settings.PeerLoginExpirationEnabled,
|
||||||
PeerLoginExpiration: time.Duration(float64(time.Second.Nanoseconds()) * float64(req.Settings.PeerLoginExpiration)),
|
PeerLoginExpiration: time.Duration(float64(time.Second.Nanoseconds()) * float64(req.Settings.PeerLoginExpiration)),
|
||||||
@@ -175,6 +179,9 @@ func (h *handler) updateAccountRequestSettings(req api.PutApiAccountsAccountIdJS
|
|||||||
|
|
||||||
PeerInactivityExpirationEnabled: req.Settings.PeerInactivityExpirationEnabled,
|
PeerInactivityExpirationEnabled: req.Settings.PeerInactivityExpirationEnabled,
|
||||||
PeerInactivityExpiration: time.Duration(float64(time.Second.Nanoseconds()) * float64(req.Settings.PeerInactivityExpiration)),
|
PeerInactivityExpiration: time.Duration(float64(time.Second.Nanoseconds()) * float64(req.Settings.PeerInactivityExpiration)),
|
||||||
|
|
||||||
|
PeerExposeEnabled: req.Settings.PeerExposeEnabled,
|
||||||
|
PeerExposeGroups: req.Settings.PeerExposeGroups,
|
||||||
}
|
}
|
||||||
|
|
||||||
if req.Settings.Extra != nil {
|
if req.Settings.Extra != nil {
|
||||||
@@ -336,6 +343,8 @@ func toAccountResponse(accountID string, settings *types.Settings, meta *types.A
|
|||||||
JwtAllowGroups: &jwtAllowGroups,
|
JwtAllowGroups: &jwtAllowGroups,
|
||||||
RegularUsersViewBlocked: settings.RegularUsersViewBlocked,
|
RegularUsersViewBlocked: settings.RegularUsersViewBlocked,
|
||||||
RoutingPeerDnsResolutionEnabled: &settings.RoutingPeerDNSResolutionEnabled,
|
RoutingPeerDnsResolutionEnabled: &settings.RoutingPeerDNSResolutionEnabled,
|
||||||
|
PeerExposeEnabled: settings.PeerExposeEnabled,
|
||||||
|
PeerExposeGroups: settings.PeerExposeGroups,
|
||||||
LazyConnectionEnabled: &settings.LazyConnectionEnabled,
|
LazyConnectionEnabled: &settings.LazyConnectionEnabled,
|
||||||
DnsDomain: &settings.DNSDomain,
|
DnsDomain: &settings.DNSDomain,
|
||||||
AutoUpdateVersion: &settings.AutoUpdateVersion,
|
AutoUpdateVersion: &settings.AutoUpdateVersion,
|
||||||
|
|||||||
@@ -414,6 +414,20 @@ func (m *testServiceManager) GetServiceIDByTargetID(_ context.Context, _, _ stri
|
|||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *testServiceManager) CreateServiceFromPeer(_ context.Context, _, _ string, _ *reverseproxy.ExposeServiceRequest) (*reverseproxy.ExposeServiceResponse, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *testServiceManager) RenewServiceFromPeer(_ context.Context, _, _, _ string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *testServiceManager) StopServiceFromPeer(_ context.Context, _, _, _ string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *testServiceManager) StartExposeReaper(_ context.Context) {}
|
||||||
|
|
||||||
func createTestState(t *testing.T, ps *nbgrpc.ProxyServiceServer, redirectURL string) string {
|
func createTestState(t *testing.T, ps *nbgrpc.ProxyServiceServer, redirectURL string) string {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ func BuildApiBlackBoxWithDBState(t testing_tools.TB, sqlFile string, expectedPee
|
|||||||
proxyMgr := proxymanager.NewManager(store)
|
proxyMgr := proxymanager.NewManager(store)
|
||||||
proxyServiceServer := nbgrpc.NewProxyServiceServer(accessLogsManager, proxyTokenStore, nbgrpc.ProxyOIDCConfig{}, peersManager, userManager, proxyMgr)
|
proxyServiceServer := nbgrpc.NewProxyServiceServer(accessLogsManager, proxyTokenStore, nbgrpc.ProxyOIDCConfig{}, peersManager, userManager, proxyMgr)
|
||||||
domainManager := manager.NewManager(store, proxyMgr, permissionsManager)
|
domainManager := manager.NewManager(store, proxyMgr, permissionsManager)
|
||||||
serviceManager := reverseproxymanager.NewManager(store, am, permissionsManager, proxyServiceServer, domainManager)
|
serviceManager := reverseproxymanager.NewManager(store, am, permissionsManager, reverseproxymanager.NewGRPCProxyController(proxyServiceServer), domainManager)
|
||||||
proxyServiceServer.SetServiceManager(serviceManager)
|
proxyServiceServer.SetServiceManager(serviceManager)
|
||||||
am.SetServiceManager(serviceManager)
|
am.SetServiceManager(serviceManager)
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
|
|
||||||
"github.com/hashicorp/go-version"
|
"github.com/hashicorp/go-version"
|
||||||
"github.com/netbirdio/netbird/idp/dex"
|
"github.com/netbirdio/netbird/idp/dex"
|
||||||
|
rpservice "github.com/netbirdio/netbird/management/internals/modules/reverseproxy/service"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/management/server/types"
|
"github.com/netbirdio/netbird/management/server/types"
|
||||||
@@ -51,6 +52,7 @@ type properties map[string]interface{}
|
|||||||
type DataSource interface {
|
type DataSource interface {
|
||||||
GetAllAccounts(ctx context.Context) []*types.Account
|
GetAllAccounts(ctx context.Context) []*types.Account
|
||||||
GetStoreEngine() types.Engine
|
GetStoreEngine() types.Engine
|
||||||
|
GetCustomDomainsCounts(ctx context.Context) (total int64, validated int64, err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConnManager peer connection manager that holds state for current active connections
|
// ConnManager peer connection manager that holds state for current active connections
|
||||||
@@ -211,6 +213,16 @@ func (w *Worker) generateProperties(ctx context.Context) properties {
|
|||||||
localUsers int
|
localUsers int
|
||||||
idpUsers int
|
idpUsers int
|
||||||
embeddedIdpTypes map[string]int
|
embeddedIdpTypes map[string]int
|
||||||
|
services int
|
||||||
|
servicesEnabled int
|
||||||
|
servicesTargets int
|
||||||
|
servicesStatusActive int
|
||||||
|
servicesStatusPending int
|
||||||
|
servicesStatusError int
|
||||||
|
servicesTargetType map[string]int
|
||||||
|
servicesAuthPassword int
|
||||||
|
servicesAuthPin int
|
||||||
|
servicesAuthOIDC int
|
||||||
)
|
)
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
metricsProperties := make(properties)
|
metricsProperties := make(properties)
|
||||||
@@ -220,10 +232,13 @@ func (w *Worker) generateProperties(ctx context.Context) properties {
|
|||||||
rulesDirection = make(map[string]int)
|
rulesDirection = make(map[string]int)
|
||||||
activeUsersLastDay = make(map[string]struct{})
|
activeUsersLastDay = make(map[string]struct{})
|
||||||
embeddedIdpTypes = make(map[string]int)
|
embeddedIdpTypes = make(map[string]int)
|
||||||
|
servicesTargetType = make(map[string]int)
|
||||||
uptime = time.Since(w.startupTime).Seconds()
|
uptime = time.Since(w.startupTime).Seconds()
|
||||||
connections := w.connManager.GetAllConnectedPeers()
|
connections := w.connManager.GetAllConnectedPeers()
|
||||||
version = nbversion.NetbirdVersion()
|
version = nbversion.NetbirdVersion()
|
||||||
|
|
||||||
|
customDomains, customDomainsValidated, _ := w.dataSource.GetCustomDomainsCounts(ctx)
|
||||||
|
|
||||||
for _, account := range w.dataSource.GetAllAccounts(ctx) {
|
for _, account := range w.dataSource.GetAllAccounts(ctx) {
|
||||||
accounts++
|
accounts++
|
||||||
|
|
||||||
@@ -335,6 +350,37 @@ func (w *Worker) generateProperties(ctx context.Context) properties {
|
|||||||
peerActiveVersions = append(peerActiveVersions, peer.Meta.WtVersion)
|
peerActiveVersions = append(peerActiveVersions, peer.Meta.WtVersion)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, service := range account.Services {
|
||||||
|
services++
|
||||||
|
if service.Enabled {
|
||||||
|
servicesEnabled++
|
||||||
|
}
|
||||||
|
servicesTargets += len(service.Targets)
|
||||||
|
|
||||||
|
switch rpservice.Status(service.Meta.Status) {
|
||||||
|
case rpservice.StatusActive:
|
||||||
|
servicesStatusActive++
|
||||||
|
case rpservice.StatusPending:
|
||||||
|
servicesStatusPending++
|
||||||
|
case rpservice.StatusError, rpservice.StatusCertificateFailed, rpservice.StatusTunnelNotCreated:
|
||||||
|
servicesStatusError++
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, target := range service.Targets {
|
||||||
|
servicesTargetType[target.TargetType]++
|
||||||
|
}
|
||||||
|
|
||||||
|
if service.Auth.PasswordAuth != nil && service.Auth.PasswordAuth.Enabled {
|
||||||
|
servicesAuthPassword++
|
||||||
|
}
|
||||||
|
if service.Auth.PinAuth != nil && service.Auth.PinAuth.Enabled {
|
||||||
|
servicesAuthPin++
|
||||||
|
}
|
||||||
|
if service.Auth.BearerAuth != nil && service.Auth.BearerAuth.Enabled {
|
||||||
|
servicesAuthOIDC++
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
minActivePeerVersion, maxActivePeerVersion := getMinMaxVersion(peerActiveVersions)
|
minActivePeerVersion, maxActivePeerVersion := getMinMaxVersion(peerActiveVersions)
|
||||||
@@ -375,6 +421,22 @@ func (w *Worker) generateProperties(ctx context.Context) properties {
|
|||||||
metricsProperties["idp_users_count"] = idpUsers
|
metricsProperties["idp_users_count"] = idpUsers
|
||||||
metricsProperties["embedded_idp_count"] = len(embeddedIdpTypes)
|
metricsProperties["embedded_idp_count"] = len(embeddedIdpTypes)
|
||||||
|
|
||||||
|
metricsProperties["services"] = services
|
||||||
|
metricsProperties["services_enabled"] = servicesEnabled
|
||||||
|
metricsProperties["services_targets"] = servicesTargets
|
||||||
|
metricsProperties["services_status_active"] = servicesStatusActive
|
||||||
|
metricsProperties["services_status_pending"] = servicesStatusPending
|
||||||
|
metricsProperties["services_status_error"] = servicesStatusError
|
||||||
|
metricsProperties["services_auth_password"] = servicesAuthPassword
|
||||||
|
metricsProperties["services_auth_pin"] = servicesAuthPin
|
||||||
|
metricsProperties["services_auth_oidc"] = servicesAuthOIDC
|
||||||
|
metricsProperties["custom_domains"] = customDomains
|
||||||
|
metricsProperties["custom_domains_validated"] = customDomainsValidated
|
||||||
|
|
||||||
|
for targetType, count := range servicesTargetType {
|
||||||
|
metricsProperties["services_target_type_"+targetType] = count
|
||||||
|
}
|
||||||
|
|
||||||
for idpType, count := range embeddedIdpTypes {
|
for idpType, count := range embeddedIdpTypes {
|
||||||
metricsProperties["embedded_idp_users_"+idpType] = count
|
metricsProperties["embedded_idp_users_"+idpType] = count
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
|
|
||||||
nbdns "github.com/netbirdio/netbird/dns"
|
nbdns "github.com/netbirdio/netbird/dns"
|
||||||
"github.com/netbirdio/netbird/idp/dex"
|
"github.com/netbirdio/netbird/idp/dex"
|
||||||
|
rpservice "github.com/netbirdio/netbird/management/internals/modules/reverseproxy/service"
|
||||||
resourceTypes "github.com/netbirdio/netbird/management/server/networks/resources/types"
|
resourceTypes "github.com/netbirdio/netbird/management/server/networks/resources/types"
|
||||||
routerTypes "github.com/netbirdio/netbird/management/server/networks/routers/types"
|
routerTypes "github.com/netbirdio/netbird/management/server/networks/routers/types"
|
||||||
networkTypes "github.com/netbirdio/netbird/management/server/networks/types"
|
networkTypes "github.com/netbirdio/netbird/management/server/networks/types"
|
||||||
@@ -115,6 +116,31 @@ func (mockDatasource) GetAllAccounts(_ context.Context) []*types.Account {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Services: []*rpservice.Service{
|
||||||
|
{
|
||||||
|
ID: "svc1",
|
||||||
|
Enabled: true,
|
||||||
|
Targets: []*rpservice.Target{
|
||||||
|
{TargetType: "peer"},
|
||||||
|
{TargetType: "host"},
|
||||||
|
},
|
||||||
|
Auth: rpservice.AuthConfig{
|
||||||
|
PasswordAuth: &rpservice.PasswordAuthConfig{Enabled: true},
|
||||||
|
},
|
||||||
|
Meta: rpservice.ServiceMeta{Status: string(rpservice.StatusActive)},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "svc2",
|
||||||
|
Enabled: false,
|
||||||
|
Targets: []*rpservice.Target{
|
||||||
|
{TargetType: "domain"},
|
||||||
|
},
|
||||||
|
Auth: rpservice.AuthConfig{
|
||||||
|
BearerAuth: &rpservice.BearerAuthConfig{Enabled: true},
|
||||||
|
},
|
||||||
|
Meta: rpservice.ServiceMeta{Status: string(rpservice.StatusPending)},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Id: "2",
|
Id: "2",
|
||||||
@@ -215,6 +241,11 @@ func (mockDatasource) GetStoreEngine() types.Engine {
|
|||||||
return types.FileStoreEngine
|
return types.FileStoreEngine
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetCustomDomainsCounts returns test custom domain counts.
|
||||||
|
func (mockDatasource) GetCustomDomainsCounts(_ context.Context) (int64, int64, error) {
|
||||||
|
return 3, 2, nil
|
||||||
|
}
|
||||||
|
|
||||||
// TestGenerateProperties tests and validate the properties generation by using the mockDatasource for the Worker.generateProperties
|
// TestGenerateProperties tests and validate the properties generation by using the mockDatasource for the Worker.generateProperties
|
||||||
func TestGenerateProperties(t *testing.T) {
|
func TestGenerateProperties(t *testing.T) {
|
||||||
ds := mockDatasource{}
|
ds := mockDatasource{}
|
||||||
@@ -347,6 +378,49 @@ func TestGenerateProperties(t *testing.T) {
|
|||||||
if properties["embedded_idp_count"] != 1 {
|
if properties["embedded_idp_count"] != 1 {
|
||||||
t.Errorf("expected 1 embedded_idp_count, got %v", properties["embedded_idp_count"])
|
t.Errorf("expected 1 embedded_idp_count, got %v", properties["embedded_idp_count"])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if properties["services"] != 2 {
|
||||||
|
t.Errorf("expected 2 services, got %v", properties["services"])
|
||||||
|
}
|
||||||
|
if properties["services_enabled"] != 1 {
|
||||||
|
t.Errorf("expected 1 services_enabled, got %v", properties["services_enabled"])
|
||||||
|
}
|
||||||
|
if properties["services_targets"] != 3 {
|
||||||
|
t.Errorf("expected 3 services_targets, got %v", properties["services_targets"])
|
||||||
|
}
|
||||||
|
if properties["services_status_active"] != 1 {
|
||||||
|
t.Errorf("expected 1 services_status_active, got %v", properties["services_status_active"])
|
||||||
|
}
|
||||||
|
if properties["services_status_pending"] != 1 {
|
||||||
|
t.Errorf("expected 1 services_status_pending, got %v", properties["services_status_pending"])
|
||||||
|
}
|
||||||
|
if properties["services_status_error"] != 0 {
|
||||||
|
t.Errorf("expected 0 services_status_error, got %v", properties["services_status_error"])
|
||||||
|
}
|
||||||
|
if properties["services_target_type_peer"] != 1 {
|
||||||
|
t.Errorf("expected 1 services_target_type_peer, got %v", properties["services_target_type_peer"])
|
||||||
|
}
|
||||||
|
if properties["services_target_type_host"] != 1 {
|
||||||
|
t.Errorf("expected 1 services_target_type_host, got %v", properties["services_target_type_host"])
|
||||||
|
}
|
||||||
|
if properties["services_target_type_domain"] != 1 {
|
||||||
|
t.Errorf("expected 1 services_target_type_domain, got %v", properties["services_target_type_domain"])
|
||||||
|
}
|
||||||
|
if properties["services_auth_password"] != 1 {
|
||||||
|
t.Errorf("expected 1 services_auth_password, got %v", properties["services_auth_password"])
|
||||||
|
}
|
||||||
|
if properties["services_auth_oidc"] != 1 {
|
||||||
|
t.Errorf("expected 1 services_auth_oidc, got %v", properties["services_auth_oidc"])
|
||||||
|
}
|
||||||
|
if properties["services_auth_pin"] != 0 {
|
||||||
|
t.Errorf("expected 0 services_auth_pin, got %v", properties["services_auth_pin"])
|
||||||
|
}
|
||||||
|
if properties["custom_domains"] != int64(3) {
|
||||||
|
t.Errorf("expected 3 custom_domains, got %v", properties["custom_domains"])
|
||||||
|
}
|
||||||
|
if properties["custom_domains_validated"] != int64(2) {
|
||||||
|
t.Errorf("expected 2 custom_domains_validated, got %v", properties["custom_domains_validated"])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExtractIdpType(t *testing.T) {
|
func TestExtractIdpType(t *testing.T) {
|
||||||
|
|||||||
@@ -407,7 +407,7 @@ func (am *MockAccountManager) AddPeer(
|
|||||||
|
|
||||||
// GetGroupByName mock implementation of GetGroupByName from server.AccountManager interface
|
// GetGroupByName mock implementation of GetGroupByName from server.AccountManager interface
|
||||||
func (am *MockAccountManager) GetGroupByName(ctx context.Context, accountID, groupName string) (*types.Group, error) {
|
func (am *MockAccountManager) GetGroupByName(ctx context.Context, accountID, groupName string) (*types.Group, error) {
|
||||||
if am.GetGroupFunc != nil {
|
if am.GetGroupByNameFunc != nil {
|
||||||
return am.GetGroupByNameFunc(ctx, accountID, groupName)
|
return am.GetGroupByNameFunc(ctx, accountID, groupName)
|
||||||
}
|
}
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method GetGroupByName is not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method GetGroupByName is not implemented")
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
"github.com/golang/mock/gomock"
|
"github.com/golang/mock/gomock"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/management/internals/modules/reverseproxy/service"
|
reverseproxy "github.com/netbirdio/netbird/management/internals/modules/reverseproxy/service"
|
||||||
"github.com/netbirdio/netbird/management/server/groups"
|
"github.com/netbirdio/netbird/management/server/groups"
|
||||||
"github.com/netbirdio/netbird/management/server/mock_server"
|
"github.com/netbirdio/netbird/management/server/mock_server"
|
||||||
"github.com/netbirdio/netbird/management/server/networks/resources/types"
|
"github.com/netbirdio/netbird/management/server/networks/resources/types"
|
||||||
|
|||||||
@@ -269,3 +269,8 @@ func (s *FileStore) GetStoreEngine() types.Engine {
|
|||||||
func (s *FileStore) SetFieldEncrypt(_ *crypt.FieldEncrypt) {
|
func (s *FileStore) SetFieldEncrypt(_ *crypt.FieldEncrypt) {
|
||||||
// no-op: FileStore stores data in plaintext JSON; encryption is not supported
|
// no-op: FileStore stores data in plaintext JSON; encryption is not supported
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetCustomDomainsCounts is a no-op for FileStore as it doesn't support custom domains.
|
||||||
|
func (s *FileStore) GetCustomDomainsCounts(_ context.Context) (int64, int64, error) {
|
||||||
|
return 0, 0, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -1008,6 +1008,18 @@ func (s *SqlStore) GetAccountsCounter(ctx context.Context) (int64, error) {
|
|||||||
return count, nil
|
return count, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetCustomDomainsCounts returns the total and validated custom domain counts.
|
||||||
|
func (s *SqlStore) GetCustomDomainsCounts(ctx context.Context) (int64, int64, error) {
|
||||||
|
var total, validated int64
|
||||||
|
if err := s.db.WithContext(ctx).Model(&domain.Domain{}).Count(&total).Error; err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
if err := s.db.WithContext(ctx).Model(&domain.Domain{}).Where("validated = ?", true).Count(&validated).Error; err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
return total, validated, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SqlStore) GetAllAccounts(ctx context.Context) (all []*types.Account) {
|
func (s *SqlStore) GetAllAccounts(ctx context.Context) (all []*types.Account) {
|
||||||
var accounts []types.Account
|
var accounts []types.Account
|
||||||
result := s.db.Find(&accounts)
|
result := s.db.Find(&accounts)
|
||||||
@@ -2115,7 +2127,8 @@ func (s *SqlStore) getServices(ctx context.Context, accountID string) ([]*rpserv
|
|||||||
s.Meta.CreatedAt = createdAt.Time
|
s.Meta.CreatedAt = createdAt.Time
|
||||||
}
|
}
|
||||||
if certIssuedAt.Valid {
|
if certIssuedAt.Valid {
|
||||||
s.Meta.CertificateIssuedAt = certIssuedAt.Time
|
t := certIssuedAt.Time
|
||||||
|
s.Meta.CertificateIssuedAt = &t
|
||||||
}
|
}
|
||||||
if status.Valid {
|
if status.Valid {
|
||||||
s.Meta.Status = status.String
|
s.Meta.Status = status.String
|
||||||
|
|||||||
@@ -277,6 +277,8 @@ type Store interface {
|
|||||||
UpdateProxyHeartbeat(ctx context.Context, proxyID string) error
|
UpdateProxyHeartbeat(ctx context.Context, proxyID string) error
|
||||||
GetActiveProxyClusterAddresses(ctx context.Context) ([]string, error)
|
GetActiveProxyClusterAddresses(ctx context.Context) ([]string, error)
|
||||||
CleanupStaleProxies(ctx context.Context, inactivityDuration time.Duration) error
|
CleanupStaleProxies(ctx context.Context, inactivityDuration time.Duration) error
|
||||||
|
|
||||||
|
GetCustomDomainsCounts(ctx context.Context) (total int64, validated int64, err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
@@ -1887,6 +1887,22 @@ func (mr *MockStoreMockRecorder) GetServiceTargetByTargetID(ctx, lockStrength, a
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetServiceTargetByTargetID", reflect.TypeOf((*MockStore)(nil).GetServiceTargetByTargetID), ctx, lockStrength, accountID, targetID)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetServiceTargetByTargetID", reflect.TypeOf((*MockStore)(nil).GetServiceTargetByTargetID), ctx, lockStrength, accountID, targetID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetCustomDomainsCounts mocks base method.
|
||||||
|
func (m *MockStore) GetCustomDomainsCounts(ctx context.Context) (int64, int64, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "GetCustomDomainsCounts", ctx)
|
||||||
|
ret0, _ := ret[0].(int64)
|
||||||
|
ret1, _ := ret[1].(int64)
|
||||||
|
ret2, _ := ret[2].(error)
|
||||||
|
return ret0, ret1, ret2
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCustomDomainsCounts indicates an expected call of GetCustomDomainsCounts.
|
||||||
|
func (mr *MockStoreMockRecorder) GetCustomDomainsCounts(ctx interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCustomDomainsCounts", reflect.TypeOf((*MockStore)(nil).GetCustomDomainsCounts), ctx)
|
||||||
|
}
|
||||||
|
|
||||||
// GetServices mocks base method.
|
// GetServices mocks base method.
|
||||||
func (m *MockStore) GetServices(ctx context.Context, lockStrength LockingStrength) ([]*service.Service, error) {
|
func (m *MockStore) GetServices(ctx context.Context, lockStrength LockingStrength) ([]*service.Service, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
|
|||||||
@@ -47,6 +47,11 @@ type Settings struct {
|
|||||||
// NetworkRange is the custom network range for that account
|
// NetworkRange is the custom network range for that account
|
||||||
NetworkRange netip.Prefix `gorm:"serializer:json"`
|
NetworkRange netip.Prefix `gorm:"serializer:json"`
|
||||||
|
|
||||||
|
// PeerExposeEnabled enables or disables peer-initiated service expose
|
||||||
|
PeerExposeEnabled bool
|
||||||
|
// PeerExposeGroups list of peer group IDs allowed to expose services
|
||||||
|
PeerExposeGroups []string `gorm:"serializer:json"`
|
||||||
|
|
||||||
// Extra is a dictionary of Account settings
|
// Extra is a dictionary of Account settings
|
||||||
Extra *ExtraSettings `gorm:"embedded;embeddedPrefix:extra_"`
|
Extra *ExtraSettings `gorm:"embedded;embeddedPrefix:extra_"`
|
||||||
|
|
||||||
@@ -80,6 +85,8 @@ func (s *Settings) Copy() *Settings {
|
|||||||
PeerInactivityExpiration: s.PeerInactivityExpiration,
|
PeerInactivityExpiration: s.PeerInactivityExpiration,
|
||||||
|
|
||||||
RoutingPeerDNSResolutionEnabled: s.RoutingPeerDNSResolutionEnabled,
|
RoutingPeerDNSResolutionEnabled: s.RoutingPeerDNSResolutionEnabled,
|
||||||
|
PeerExposeEnabled: s.PeerExposeEnabled,
|
||||||
|
PeerExposeGroups: slices.Clone(s.PeerExposeGroups),
|
||||||
LazyConnectionEnabled: s.LazyConnectionEnabled,
|
LazyConnectionEnabled: s.LazyConnectionEnabled,
|
||||||
DNSDomain: s.DNSDomain,
|
DNSDomain: s.DNSDomain,
|
||||||
NetworkRange: s.NetworkRange,
|
NetworkRange: s.NetworkRange,
|
||||||
|
|||||||
@@ -300,6 +300,20 @@ func (m *storeBackedServiceManager) GetServiceIDByTargetID(ctx context.Context,
|
|||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *storeBackedServiceManager) CreateServiceFromPeer(_ context.Context, _, _ string, _ *reverseproxy.ExposeServiceRequest) (*reverseproxy.ExposeServiceResponse, error) {
|
||||||
|
return &reverseproxy.ExposeServiceResponse{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *storeBackedServiceManager) RenewServiceFromPeer(_ context.Context, _, _, _ string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *storeBackedServiceManager) StopServiceFromPeer(_ context.Context, _, _, _ string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *storeBackedServiceManager) StartExposeReaper(_ context.Context) {}
|
||||||
|
|
||||||
func strPtr(s string) *string {
|
func strPtr(s string) *string {
|
||||||
return &s
|
return &s
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"github.com/netbirdio/netbird/shared/management/proto"
|
"github.com/netbirdio/netbird/shared/management/proto"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Client is the interface for the management service client.
|
||||||
type Client interface {
|
type Client interface {
|
||||||
io.Closer
|
io.Closer
|
||||||
Sync(ctx context.Context, sysInfo *system.Info, msgHandler func(msg *proto.SyncResponse) error) error
|
Sync(ctx context.Context, sysInfo *system.Info, msgHandler func(msg *proto.SyncResponse) error) error
|
||||||
@@ -24,4 +25,7 @@ type Client interface {
|
|||||||
IsHealthy() bool
|
IsHealthy() bool
|
||||||
SyncMeta(sysInfo *system.Info) error
|
SyncMeta(sysInfo *system.Info) error
|
||||||
Logout() error
|
Logout() error
|
||||||
|
CreateExpose(ctx context.Context, req ExposeRequest) (*ExposeResponse, error)
|
||||||
|
RenewExpose(ctx context.Context, domain string) error
|
||||||
|
StopExpose(ctx context.Context, domain string) error
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,6 +48,22 @@ type GrpcClient struct {
|
|||||||
connStateCallbackLock sync.RWMutex
|
connStateCallbackLock sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ExposeRequest struct {
|
||||||
|
NamePrefix string
|
||||||
|
Domain string
|
||||||
|
Port uint16
|
||||||
|
Protocol int
|
||||||
|
Pin string
|
||||||
|
Password string
|
||||||
|
UserGroups []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExposeResponse struct {
|
||||||
|
ServiceName string
|
||||||
|
Domain string
|
||||||
|
ServiceURL string
|
||||||
|
}
|
||||||
|
|
||||||
// NewClient creates a new client to Management service
|
// NewClient creates a new client to Management service
|
||||||
func NewClient(ctx context.Context, addr string, ourPrivateKey wgtypes.Key, tlsEnabled bool) (*GrpcClient, error) {
|
func NewClient(ctx context.Context, addr string, ourPrivateKey wgtypes.Key, tlsEnabled bool) (*GrpcClient, error) {
|
||||||
var conn *grpc.ClientConn
|
var conn *grpc.ClientConn
|
||||||
@@ -690,6 +706,123 @@ func (c *GrpcClient) Logout() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateExpose calls the management server to create a new expose service.
|
||||||
|
func (c *GrpcClient) CreateExpose(ctx context.Context, req ExposeRequest) (*ExposeResponse, error) {
|
||||||
|
serverPubKey, err := c.GetServerPublicKey()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
protoReq, err := toProtoExposeServiceRequest(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
encReq, err := encryption.EncryptMessage(*serverPubKey, c.key, protoReq)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("encrypt create expose request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
mgmCtx, cancel := context.WithTimeout(ctx, ConnectTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
resp, err := c.realClient.CreateExpose(mgmCtx, &proto.EncryptedMessage{
|
||||||
|
WgPubKey: c.key.PublicKey().String(),
|
||||||
|
Body: encReq,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
exposeResp := &proto.ExposeServiceResponse{}
|
||||||
|
if err := encryption.DecryptMessage(*serverPubKey, c.key, resp.Body, exposeResp); err != nil {
|
||||||
|
return nil, fmt.Errorf("decrypt create expose response: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fromProtoExposeResponse(exposeResp), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RenewExpose extends the TTL of an active expose session on the management server.
|
||||||
|
func (c *GrpcClient) RenewExpose(ctx context.Context, domain string) error {
|
||||||
|
serverPubKey, err := c.GetServerPublicKey()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
req := &proto.RenewExposeRequest{Domain: domain}
|
||||||
|
encReq, err := encryption.EncryptMessage(*serverPubKey, c.key, req)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("encrypt renew expose request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
mgmCtx, cancel := context.WithTimeout(ctx, ConnectTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
_, err = c.realClient.RenewExpose(mgmCtx, &proto.EncryptedMessage{
|
||||||
|
WgPubKey: c.key.PublicKey().String(),
|
||||||
|
Body: encReq,
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// StopExpose terminates an active expose session on the management server.
|
||||||
|
func (c *GrpcClient) StopExpose(ctx context.Context, domain string) error {
|
||||||
|
serverPubKey, err := c.GetServerPublicKey()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
req := &proto.StopExposeRequest{Domain: domain}
|
||||||
|
encReq, err := encryption.EncryptMessage(*serverPubKey, c.key, req)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("encrypt stop expose request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
mgmCtx, cancel := context.WithTimeout(ctx, ConnectTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
_, err = c.realClient.StopExpose(mgmCtx, &proto.EncryptedMessage{
|
||||||
|
WgPubKey: c.key.PublicKey().String(),
|
||||||
|
Body: encReq,
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func fromProtoExposeResponse(resp *proto.ExposeServiceResponse) *ExposeResponse {
|
||||||
|
return &ExposeResponse{
|
||||||
|
ServiceName: resp.ServiceName,
|
||||||
|
Domain: resp.Domain,
|
||||||
|
ServiceURL: resp.ServiceUrl,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func toProtoExposeServiceRequest(req ExposeRequest) (*proto.ExposeServiceRequest, error) {
|
||||||
|
var protocol proto.ExposeProtocol
|
||||||
|
|
||||||
|
switch req.Protocol {
|
||||||
|
case int(proto.ExposeProtocol_EXPOSE_HTTP):
|
||||||
|
protocol = proto.ExposeProtocol_EXPOSE_HTTP
|
||||||
|
case int(proto.ExposeProtocol_EXPOSE_HTTPS):
|
||||||
|
protocol = proto.ExposeProtocol_EXPOSE_HTTPS
|
||||||
|
case int(proto.ExposeProtocol_EXPOSE_TCP):
|
||||||
|
protocol = proto.ExposeProtocol_EXPOSE_TCP
|
||||||
|
case int(proto.ExposeProtocol_EXPOSE_UDP):
|
||||||
|
protocol = proto.ExposeProtocol_EXPOSE_UDP
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("invalid expose protocol: %d", req.Protocol)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &proto.ExposeServiceRequest{
|
||||||
|
NamePrefix: req.NamePrefix,
|
||||||
|
Domain: req.Domain,
|
||||||
|
Port: uint32(req.Port),
|
||||||
|
Protocol: protocol,
|
||||||
|
Pin: req.Pin,
|
||||||
|
Password: req.Password,
|
||||||
|
UserGroups: req.UserGroups,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func infoToMetaData(info *system.Info) *proto.PeerSystemMeta {
|
func infoToMetaData(info *system.Info) *proto.PeerSystemMeta {
|
||||||
if info == nil {
|
if info == nil {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"github.com/netbirdio/netbird/shared/management/proto"
|
"github.com/netbirdio/netbird/shared/management/proto"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// MockClient is a mock implementation of the Client interface for testing.
|
||||||
type MockClient struct {
|
type MockClient struct {
|
||||||
CloseFunc func() error
|
CloseFunc func() error
|
||||||
SyncFunc func(ctx context.Context, sysInfo *system.Info, msgHandler func(msg *proto.SyncResponse) error) error
|
SyncFunc func(ctx context.Context, sysInfo *system.Info, msgHandler func(msg *proto.SyncResponse) error) error
|
||||||
@@ -21,6 +22,9 @@ type MockClient struct {
|
|||||||
SyncMetaFunc func(sysInfo *system.Info) error
|
SyncMetaFunc func(sysInfo *system.Info) error
|
||||||
LogoutFunc func() error
|
LogoutFunc func() error
|
||||||
JobFunc func(ctx context.Context, msgHandler func(msg *proto.JobRequest) *proto.JobResponse) error
|
JobFunc func(ctx context.Context, msgHandler func(msg *proto.JobRequest) *proto.JobResponse) error
|
||||||
|
CreateExposeFunc func(ctx context.Context, req ExposeRequest) (*ExposeResponse, error)
|
||||||
|
RenewExposeFunc func(ctx context.Context, domain string) error
|
||||||
|
StopExposeFunc func(ctx context.Context, domain string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MockClient) IsHealthy() bool {
|
func (m *MockClient) IsHealthy() bool {
|
||||||
@@ -80,10 +84,10 @@ func (m *MockClient) GetPKCEAuthorizationFlow(serverKey wgtypes.Key) (*proto.PKC
|
|||||||
if m.GetPKCEAuthorizationFlowFunc == nil {
|
if m.GetPKCEAuthorizationFlowFunc == nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
return m.GetPKCEAuthorizationFlow(serverKey)
|
return m.GetPKCEAuthorizationFlowFunc(serverKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetNetworkMap mock implementation of GetNetworkMap from mgm.Client interface
|
// GetNetworkMap mock implementation of GetNetworkMap from Client interface.
|
||||||
func (m *MockClient) GetNetworkMap(_ *system.Info) (*proto.NetworkMap, error) {
|
func (m *MockClient) GetNetworkMap(_ *system.Info) (*proto.NetworkMap, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
@@ -101,3 +105,24 @@ func (m *MockClient) Logout() error {
|
|||||||
}
|
}
|
||||||
return m.LogoutFunc()
|
return m.LogoutFunc()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *MockClient) CreateExpose(ctx context.Context, req ExposeRequest) (*ExposeResponse, error) {
|
||||||
|
if m.CreateExposeFunc == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return m.CreateExposeFunc(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockClient) RenewExpose(ctx context.Context, domain string) error {
|
||||||
|
if m.RenewExposeFunc == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return m.RenewExposeFunc(ctx, domain)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockClient) StopExpose(ctx context.Context, domain string) error {
|
||||||
|
if m.StopExposeFunc == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return m.StopExposeFunc(ctx, domain)
|
||||||
|
}
|
||||||
|
|||||||
@@ -326,6 +326,16 @@ components:
|
|||||||
type: string
|
type: string
|
||||||
format: cidr
|
format: cidr
|
||||||
example: 100.64.0.0/16
|
example: 100.64.0.0/16
|
||||||
|
peer_expose_enabled:
|
||||||
|
description: Enables or disables peer expose. If enabled, peers can expose local services through the reverse proxy using the CLI.
|
||||||
|
type: boolean
|
||||||
|
example: false
|
||||||
|
peer_expose_groups:
|
||||||
|
description: Limits which peer groups are allowed to expose services. If empty, all peers are allowed when peer expose is enabled.
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
example: ch8i4ug6lnn4g9hqv7m0
|
||||||
extra:
|
extra:
|
||||||
$ref: '#/components/schemas/AccountExtraSettings'
|
$ref: '#/components/schemas/AccountExtraSettings'
|
||||||
lazy_connection_enabled:
|
lazy_connection_enabled:
|
||||||
@@ -353,6 +363,8 @@ components:
|
|||||||
- peer_inactivity_expiration_enabled
|
- peer_inactivity_expiration_enabled
|
||||||
- peer_inactivity_expiration
|
- peer_inactivity_expiration
|
||||||
- regular_users_view_blocked
|
- regular_users_view_blocked
|
||||||
|
- peer_expose_enabled
|
||||||
|
- peer_expose_groups
|
||||||
AccountExtraSettings:
|
AccountExtraSettings:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
|||||||
@@ -512,6 +512,12 @@ type AccountSettings struct {
|
|||||||
// NetworkRange Allows to define a custom network range for the account in CIDR format
|
// NetworkRange Allows to define a custom network range for the account in CIDR format
|
||||||
NetworkRange *string `json:"network_range,omitempty"`
|
NetworkRange *string `json:"network_range,omitempty"`
|
||||||
|
|
||||||
|
// PeerExposeEnabled Enables or disables peer expose. If enabled, peers can expose local services through the reverse proxy using the CLI.
|
||||||
|
PeerExposeEnabled bool `json:"peer_expose_enabled"`
|
||||||
|
|
||||||
|
// PeerExposeGroups Limits which peer groups are allowed to expose services. If empty, all peers are allowed when peer expose is enabled.
|
||||||
|
PeerExposeGroups []string `json:"peer_expose_groups"`
|
||||||
|
|
||||||
// PeerInactivityExpiration Period of time of inactivity after which peer session expires (seconds).
|
// PeerInactivityExpiration Period of time of inactivity after which peer session expires (seconds).
|
||||||
PeerInactivityExpiration int `json:"peer_inactivity_expiration"`
|
PeerInactivityExpiration int `json:"peer_inactivity_expiration"`
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.26.0
|
// protoc-gen-go v1.26.0
|
||||||
// protoc v6.33.0
|
// protoc v6.33.3
|
||||||
// source: management.proto
|
// source: management.proto
|
||||||
|
|
||||||
package proto
|
package proto
|
||||||
@@ -221,6 +221,58 @@ func (RuleAction) EnumDescriptor() ([]byte, []int) {
|
|||||||
return file_management_proto_rawDescGZIP(), []int{3}
|
return file_management_proto_rawDescGZIP(), []int{3}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ExposeProtocol int32
|
||||||
|
|
||||||
|
const (
|
||||||
|
ExposeProtocol_EXPOSE_HTTP ExposeProtocol = 0
|
||||||
|
ExposeProtocol_EXPOSE_HTTPS ExposeProtocol = 1
|
||||||
|
ExposeProtocol_EXPOSE_TCP ExposeProtocol = 2
|
||||||
|
ExposeProtocol_EXPOSE_UDP ExposeProtocol = 3
|
||||||
|
)
|
||||||
|
|
||||||
|
// Enum value maps for ExposeProtocol.
|
||||||
|
var (
|
||||||
|
ExposeProtocol_name = map[int32]string{
|
||||||
|
0: "EXPOSE_HTTP",
|
||||||
|
1: "EXPOSE_HTTPS",
|
||||||
|
2: "EXPOSE_TCP",
|
||||||
|
3: "EXPOSE_UDP",
|
||||||
|
}
|
||||||
|
ExposeProtocol_value = map[string]int32{
|
||||||
|
"EXPOSE_HTTP": 0,
|
||||||
|
"EXPOSE_HTTPS": 1,
|
||||||
|
"EXPOSE_TCP": 2,
|
||||||
|
"EXPOSE_UDP": 3,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (x ExposeProtocol) Enum() *ExposeProtocol {
|
||||||
|
p := new(ExposeProtocol)
|
||||||
|
*p = x
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x ExposeProtocol) String() string {
|
||||||
|
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ExposeProtocol) Descriptor() protoreflect.EnumDescriptor {
|
||||||
|
return file_management_proto_enumTypes[4].Descriptor()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ExposeProtocol) Type() protoreflect.EnumType {
|
||||||
|
return &file_management_proto_enumTypes[4]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x ExposeProtocol) Number() protoreflect.EnumNumber {
|
||||||
|
return protoreflect.EnumNumber(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use ExposeProtocol.Descriptor instead.
|
||||||
|
func (ExposeProtocol) EnumDescriptor() ([]byte, []int) {
|
||||||
|
return file_management_proto_rawDescGZIP(), []int{4}
|
||||||
|
}
|
||||||
|
|
||||||
type HostConfig_Protocol int32
|
type HostConfig_Protocol int32
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -260,11 +312,11 @@ func (x HostConfig_Protocol) String() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (HostConfig_Protocol) Descriptor() protoreflect.EnumDescriptor {
|
func (HostConfig_Protocol) Descriptor() protoreflect.EnumDescriptor {
|
||||||
return file_management_proto_enumTypes[4].Descriptor()
|
return file_management_proto_enumTypes[5].Descriptor()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (HostConfig_Protocol) Type() protoreflect.EnumType {
|
func (HostConfig_Protocol) Type() protoreflect.EnumType {
|
||||||
return &file_management_proto_enumTypes[4]
|
return &file_management_proto_enumTypes[5]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x HostConfig_Protocol) Number() protoreflect.EnumNumber {
|
func (x HostConfig_Protocol) Number() protoreflect.EnumNumber {
|
||||||
@@ -303,11 +355,11 @@ func (x DeviceAuthorizationFlowProvider) String() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (DeviceAuthorizationFlowProvider) Descriptor() protoreflect.EnumDescriptor {
|
func (DeviceAuthorizationFlowProvider) Descriptor() protoreflect.EnumDescriptor {
|
||||||
return file_management_proto_enumTypes[5].Descriptor()
|
return file_management_proto_enumTypes[6].Descriptor()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (DeviceAuthorizationFlowProvider) Type() protoreflect.EnumType {
|
func (DeviceAuthorizationFlowProvider) Type() protoreflect.EnumType {
|
||||||
return &file_management_proto_enumTypes[5]
|
return &file_management_proto_enumTypes[6]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x DeviceAuthorizationFlowProvider) Number() protoreflect.EnumNumber {
|
func (x DeviceAuthorizationFlowProvider) Number() protoreflect.EnumNumber {
|
||||||
@@ -3983,6 +4035,334 @@ func (x *ForwardingRule) GetTranslatedPort() *PortInfo {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ExposeServiceRequest struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
Port uint32 `protobuf:"varint,1,opt,name=port,proto3" json:"port,omitempty"`
|
||||||
|
Protocol ExposeProtocol `protobuf:"varint,2,opt,name=protocol,proto3,enum=management.ExposeProtocol" json:"protocol,omitempty"`
|
||||||
|
Pin string `protobuf:"bytes,3,opt,name=pin,proto3" json:"pin,omitempty"`
|
||||||
|
Password string `protobuf:"bytes,4,opt,name=password,proto3" json:"password,omitempty"`
|
||||||
|
UserGroups []string `protobuf:"bytes,5,rep,name=user_groups,json=userGroups,proto3" json:"user_groups,omitempty"`
|
||||||
|
Domain string `protobuf:"bytes,6,opt,name=domain,proto3" json:"domain,omitempty"`
|
||||||
|
NamePrefix string `protobuf:"bytes,7,opt,name=name_prefix,json=namePrefix,proto3" json:"name_prefix,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ExposeServiceRequest) Reset() {
|
||||||
|
*x = ExposeServiceRequest{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_management_proto_msgTypes[47]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ExposeServiceRequest) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*ExposeServiceRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *ExposeServiceRequest) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_management_proto_msgTypes[47]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use ExposeServiceRequest.ProtoReflect.Descriptor instead.
|
||||||
|
func (*ExposeServiceRequest) Descriptor() ([]byte, []int) {
|
||||||
|
return file_management_proto_rawDescGZIP(), []int{47}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ExposeServiceRequest) GetPort() uint32 {
|
||||||
|
if x != nil {
|
||||||
|
return x.Port
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ExposeServiceRequest) GetProtocol() ExposeProtocol {
|
||||||
|
if x != nil {
|
||||||
|
return x.Protocol
|
||||||
|
}
|
||||||
|
return ExposeProtocol_EXPOSE_HTTP
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ExposeServiceRequest) GetPin() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Pin
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ExposeServiceRequest) GetPassword() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Password
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ExposeServiceRequest) GetUserGroups() []string {
|
||||||
|
if x != nil {
|
||||||
|
return x.UserGroups
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ExposeServiceRequest) GetDomain() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Domain
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ExposeServiceRequest) GetNamePrefix() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.NamePrefix
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExposeServiceResponse struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
ServiceName string `protobuf:"bytes,1,opt,name=service_name,json=serviceName,proto3" json:"service_name,omitempty"`
|
||||||
|
ServiceUrl string `protobuf:"bytes,2,opt,name=service_url,json=serviceUrl,proto3" json:"service_url,omitempty"`
|
||||||
|
Domain string `protobuf:"bytes,3,opt,name=domain,proto3" json:"domain,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ExposeServiceResponse) Reset() {
|
||||||
|
*x = ExposeServiceResponse{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_management_proto_msgTypes[48]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ExposeServiceResponse) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*ExposeServiceResponse) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *ExposeServiceResponse) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_management_proto_msgTypes[48]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use ExposeServiceResponse.ProtoReflect.Descriptor instead.
|
||||||
|
func (*ExposeServiceResponse) Descriptor() ([]byte, []int) {
|
||||||
|
return file_management_proto_rawDescGZIP(), []int{48}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ExposeServiceResponse) GetServiceName() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.ServiceName
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ExposeServiceResponse) GetServiceUrl() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.ServiceUrl
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ExposeServiceResponse) GetDomain() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Domain
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type RenewExposeRequest struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
Domain string `protobuf:"bytes,1,opt,name=domain,proto3" json:"domain,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *RenewExposeRequest) Reset() {
|
||||||
|
*x = RenewExposeRequest{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_management_proto_msgTypes[49]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *RenewExposeRequest) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*RenewExposeRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *RenewExposeRequest) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_management_proto_msgTypes[49]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use RenewExposeRequest.ProtoReflect.Descriptor instead.
|
||||||
|
func (*RenewExposeRequest) Descriptor() ([]byte, []int) {
|
||||||
|
return file_management_proto_rawDescGZIP(), []int{49}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *RenewExposeRequest) GetDomain() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Domain
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type RenewExposeResponse struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *RenewExposeResponse) Reset() {
|
||||||
|
*x = RenewExposeResponse{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_management_proto_msgTypes[50]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *RenewExposeResponse) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*RenewExposeResponse) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *RenewExposeResponse) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_management_proto_msgTypes[50]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use RenewExposeResponse.ProtoReflect.Descriptor instead.
|
||||||
|
func (*RenewExposeResponse) Descriptor() ([]byte, []int) {
|
||||||
|
return file_management_proto_rawDescGZIP(), []int{50}
|
||||||
|
}
|
||||||
|
|
||||||
|
type StopExposeRequest struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
Domain string `protobuf:"bytes,1,opt,name=domain,proto3" json:"domain,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *StopExposeRequest) Reset() {
|
||||||
|
*x = StopExposeRequest{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_management_proto_msgTypes[51]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *StopExposeRequest) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*StopExposeRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *StopExposeRequest) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_management_proto_msgTypes[51]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use StopExposeRequest.ProtoReflect.Descriptor instead.
|
||||||
|
func (*StopExposeRequest) Descriptor() ([]byte, []int) {
|
||||||
|
return file_management_proto_rawDescGZIP(), []int{51}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *StopExposeRequest) GetDomain() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Domain
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type StopExposeResponse struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *StopExposeResponse) Reset() {
|
||||||
|
*x = StopExposeResponse{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_management_proto_msgTypes[52]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *StopExposeResponse) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*StopExposeResponse) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *StopExposeResponse) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_management_proto_msgTypes[52]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use StopExposeResponse.ProtoReflect.Descriptor instead.
|
||||||
|
func (*StopExposeResponse) Descriptor() ([]byte, []int) {
|
||||||
|
return file_management_proto_rawDescGZIP(), []int{52}
|
||||||
|
}
|
||||||
|
|
||||||
type PortInfo_Range struct {
|
type PortInfo_Range struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
@@ -3995,7 +4375,7 @@ type PortInfo_Range struct {
|
|||||||
func (x *PortInfo_Range) Reset() {
|
func (x *PortInfo_Range) Reset() {
|
||||||
*x = PortInfo_Range{}
|
*x = PortInfo_Range{}
|
||||||
if protoimpl.UnsafeEnabled {
|
if protoimpl.UnsafeEnabled {
|
||||||
mi := &file_management_proto_msgTypes[48]
|
mi := &file_management_proto_msgTypes[54]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@@ -4008,7 +4388,7 @@ func (x *PortInfo_Range) String() string {
|
|||||||
func (*PortInfo_Range) ProtoMessage() {}
|
func (*PortInfo_Range) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *PortInfo_Range) ProtoReflect() protoreflect.Message {
|
func (x *PortInfo_Range) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_management_proto_msgTypes[48]
|
mi := &file_management_proto_msgTypes[54]
|
||||||
if protoimpl.UnsafeEnabled && x != nil {
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@@ -4616,62 +4996,113 @@ var file_management_proto_rawDesc = []byte{
|
|||||||
0x64, 0x50, 0x6f, 0x72, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6d, 0x61,
|
0x64, 0x50, 0x6f, 0x72, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6d, 0x61,
|
||||||
0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x6f, 0x72, 0x74, 0x49, 0x6e, 0x66,
|
0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x6f, 0x72, 0x74, 0x49, 0x6e, 0x66,
|
||||||
0x6f, 0x52, 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x50, 0x6f, 0x72,
|
0x6f, 0x52, 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x50, 0x6f, 0x72,
|
||||||
0x74, 0x2a, 0x3a, 0x0a, 0x09, 0x4a, 0x6f, 0x62, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x12,
|
0x74, 0x22, 0xea, 0x01, 0x0a, 0x14, 0x45, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x53, 0x65, 0x72, 0x76,
|
||||||
0x0a, 0x0e, 0x75, 0x6e, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73,
|
0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6f,
|
||||||
0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x73, 0x75, 0x63, 0x63, 0x65, 0x65, 0x64, 0x65, 0x64, 0x10,
|
0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x36,
|
||||||
0x01, 0x12, 0x0a, 0x0a, 0x06, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x10, 0x02, 0x2a, 0x4c, 0x0a,
|
0x0a, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e,
|
||||||
0x0c, 0x52, 0x75, 0x6c, 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x0b, 0x0a,
|
0x32, 0x1a, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x78,
|
||||||
0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x41, 0x4c,
|
0x70, 0x6f, 0x73, 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x52, 0x08, 0x70, 0x72,
|
||||||
0x4c, 0x10, 0x01, 0x12, 0x07, 0x0a, 0x03, 0x54, 0x43, 0x50, 0x10, 0x02, 0x12, 0x07, 0x0a, 0x03,
|
0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x10, 0x0a, 0x03, 0x70, 0x69, 0x6e, 0x18, 0x03, 0x20,
|
||||||
0x55, 0x44, 0x50, 0x10, 0x03, 0x12, 0x08, 0x0a, 0x04, 0x49, 0x43, 0x4d, 0x50, 0x10, 0x04, 0x12,
|
0x01, 0x28, 0x09, 0x52, 0x03, 0x70, 0x69, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73,
|
||||||
0x0a, 0x0a, 0x06, 0x43, 0x55, 0x53, 0x54, 0x4f, 0x4d, 0x10, 0x05, 0x2a, 0x20, 0x0a, 0x0d, 0x52,
|
0x77, 0x6f, 0x72, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73,
|
||||||
0x75, 0x6c, 0x65, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x06, 0x0a, 0x02,
|
0x77, 0x6f, 0x72, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x67, 0x72, 0x6f,
|
||||||
0x49, 0x4e, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x4f, 0x55, 0x54, 0x10, 0x01, 0x2a, 0x22, 0x0a,
|
0x75, 0x70, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x75, 0x73, 0x65, 0x72, 0x47,
|
||||||
0x0a, 0x52, 0x75, 0x6c, 0x65, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0a, 0x0a, 0x06, 0x41,
|
0x72, 0x6f, 0x75, 0x70, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18,
|
||||||
0x43, 0x43, 0x45, 0x50, 0x54, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x44, 0x52, 0x4f, 0x50, 0x10,
|
0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x1f, 0x0a,
|
||||||
0x01, 0x32, 0x96, 0x05, 0x0a, 0x11, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74,
|
0x0b, 0x6e, 0x61, 0x6d, 0x65, 0x5f, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x18, 0x07, 0x20, 0x01,
|
||||||
0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x45, 0x0a, 0x05, 0x4c, 0x6f, 0x67, 0x69, 0x6e,
|
0x28, 0x09, 0x52, 0x0a, 0x6e, 0x61, 0x6d, 0x65, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, 0x22, 0x73,
|
||||||
0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e,
|
0x0a, 0x15, 0x45, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x52,
|
||||||
0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c,
|
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x69,
|
||||||
0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72,
|
0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73,
|
||||||
0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x12, 0x46,
|
0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x65,
|
||||||
0x0a, 0x04, 0x53, 0x79, 0x6e, 0x63, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d,
|
0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||||
|
0x0a, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x55, 0x72, 0x6c, 0x12, 0x16, 0x0a, 0x06, 0x64,
|
||||||
|
0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d,
|
||||||
|
0x61, 0x69, 0x6e, 0x22, 0x2c, 0x0a, 0x12, 0x52, 0x65, 0x6e, 0x65, 0x77, 0x45, 0x78, 0x70, 0x6f,
|
||||||
|
0x73, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d,
|
||||||
|
0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69,
|
||||||
|
0x6e, 0x22, 0x15, 0x0a, 0x13, 0x52, 0x65, 0x6e, 0x65, 0x77, 0x45, 0x78, 0x70, 0x6f, 0x73, 0x65,
|
||||||
|
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2b, 0x0a, 0x11, 0x53, 0x74, 0x6f, 0x70,
|
||||||
|
0x45, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a,
|
||||||
|
0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64,
|
||||||
|
0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x22, 0x14, 0x0a, 0x12, 0x53, 0x74, 0x6f, 0x70, 0x45, 0x78, 0x70,
|
||||||
|
0x6f, 0x73, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2a, 0x3a, 0x0a, 0x09, 0x4a,
|
||||||
|
0x6f, 0x62, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x12, 0x0a, 0x0e, 0x75, 0x6e, 0x6b, 0x6e,
|
||||||
|
0x6f, 0x77, 0x6e, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09,
|
||||||
|
0x73, 0x75, 0x63, 0x63, 0x65, 0x65, 0x64, 0x65, 0x64, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x66,
|
||||||
|
0x61, 0x69, 0x6c, 0x65, 0x64, 0x10, 0x02, 0x2a, 0x4c, 0x0a, 0x0c, 0x52, 0x75, 0x6c, 0x65, 0x50,
|
||||||
|
0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f,
|
||||||
|
0x57, 0x4e, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x41, 0x4c, 0x4c, 0x10, 0x01, 0x12, 0x07, 0x0a,
|
||||||
|
0x03, 0x54, 0x43, 0x50, 0x10, 0x02, 0x12, 0x07, 0x0a, 0x03, 0x55, 0x44, 0x50, 0x10, 0x03, 0x12,
|
||||||
|
0x08, 0x0a, 0x04, 0x49, 0x43, 0x4d, 0x50, 0x10, 0x04, 0x12, 0x0a, 0x0a, 0x06, 0x43, 0x55, 0x53,
|
||||||
|
0x54, 0x4f, 0x4d, 0x10, 0x05, 0x2a, 0x20, 0x0a, 0x0d, 0x52, 0x75, 0x6c, 0x65, 0x44, 0x69, 0x72,
|
||||||
|
0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x06, 0x0a, 0x02, 0x49, 0x4e, 0x10, 0x00, 0x12, 0x07,
|
||||||
|
0x0a, 0x03, 0x4f, 0x55, 0x54, 0x10, 0x01, 0x2a, 0x22, 0x0a, 0x0a, 0x52, 0x75, 0x6c, 0x65, 0x41,
|
||||||
|
0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0a, 0x0a, 0x06, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x10,
|
||||||
|
0x00, 0x12, 0x08, 0x0a, 0x04, 0x44, 0x52, 0x4f, 0x50, 0x10, 0x01, 0x2a, 0x53, 0x0a, 0x0e, 0x45,
|
||||||
|
0x78, 0x70, 0x6f, 0x73, 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x0f, 0x0a,
|
||||||
|
0x0b, 0x45, 0x58, 0x50, 0x4f, 0x53, 0x45, 0x5f, 0x48, 0x54, 0x54, 0x50, 0x10, 0x00, 0x12, 0x10,
|
||||||
|
0x0a, 0x0c, 0x45, 0x58, 0x50, 0x4f, 0x53, 0x45, 0x5f, 0x48, 0x54, 0x54, 0x50, 0x53, 0x10, 0x01,
|
||||||
|
0x12, 0x0e, 0x0a, 0x0a, 0x45, 0x58, 0x50, 0x4f, 0x53, 0x45, 0x5f, 0x54, 0x43, 0x50, 0x10, 0x02,
|
||||||
|
0x12, 0x0e, 0x0a, 0x0a, 0x45, 0x58, 0x50, 0x4f, 0x53, 0x45, 0x5f, 0x55, 0x44, 0x50, 0x10, 0x03,
|
||||||
|
0x32, 0xfd, 0x06, 0x0a, 0x11, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53,
|
||||||
|
0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x45, 0x0a, 0x05, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12,
|
||||||
|
0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63,
|
||||||
|
0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e,
|
||||||
|
0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79,
|
||||||
|
0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x12, 0x46, 0x0a,
|
||||||
|
0x04, 0x53, 0x79, 0x6e, 0x63, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65,
|
||||||
|
0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73,
|
||||||
|
0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74,
|
||||||
|
0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67,
|
||||||
|
0x65, 0x22, 0x00, 0x30, 0x01, 0x12, 0x42, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76,
|
||||||
|
0x65, 0x72, 0x4b, 0x65, 0x79, 0x12, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65,
|
||||||
|
0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1d, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67,
|
||||||
|
0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x52,
|
||||||
|
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x33, 0x0a, 0x09, 0x69, 0x73, 0x48,
|
||||||
|
0x65, 0x61, 0x6c, 0x74, 0x68, 0x79, 0x12, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d,
|
||||||
|
0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61,
|
||||||
|
0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x5a,
|
||||||
|
0x0a, 0x1a, 0x47, 0x65, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f,
|
||||||
|
0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x12, 0x1c, 0x2e, 0x6d,
|
||||||
|
0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70,
|
||||||
|
0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e,
|
||||||
|
0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65,
|
||||||
|
0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x12, 0x58, 0x0a, 0x18, 0x47, 0x65,
|
||||||
|
0x74, 0x50, 0x4b, 0x43, 0x45, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69,
|
||||||
|
0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d,
|
||||||
0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73,
|
0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73,
|
||||||
0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e,
|
0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e,
|
||||||
0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61,
|
0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61,
|
||||||
0x67, 0x65, 0x22, 0x00, 0x30, 0x01, 0x12, 0x42, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x53, 0x65, 0x72,
|
0x67, 0x65, 0x22, 0x00, 0x12, 0x3d, 0x0a, 0x08, 0x53, 0x79, 0x6e, 0x63, 0x4d, 0x65, 0x74, 0x61,
|
||||||
0x76, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x12, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d,
|
0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e,
|
||||||
0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1d, 0x2e, 0x6d, 0x61, 0x6e, 0x61,
|
0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x11,
|
||||||
0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4b, 0x65, 0x79,
|
0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74,
|
||||||
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x33, 0x0a, 0x09, 0x69, 0x73,
|
0x79, 0x22, 0x00, 0x12, 0x3b, 0x0a, 0x06, 0x4c, 0x6f, 0x67, 0x6f, 0x75, 0x74, 0x12, 0x1c, 0x2e,
|
||||||
0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x79, 0x12, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65,
|
|
||||||
0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x11, 0x2e, 0x6d, 0x61, 0x6e,
|
|
||||||
0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12,
|
|
||||||
0x5a, 0x0a, 0x1a, 0x47, 0x65, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x41, 0x75, 0x74, 0x68,
|
|
||||||
0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x12, 0x1c, 0x2e,
|
|
||||||
0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79,
|
0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79,
|
||||||
0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61,
|
0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x11, 0x2e, 0x6d, 0x61,
|
||||||
0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74,
|
0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00,
|
||||||
0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x12, 0x58, 0x0a, 0x18, 0x47,
|
0x12, 0x47, 0x0a, 0x03, 0x4a, 0x6f, 0x62, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65,
|
||||||
0x65, 0x74, 0x50, 0x4b, 0x43, 0x45, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74,
|
|
||||||
0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65,
|
|
||||||
0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65,
|
0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65,
|
||||||
0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65,
|
0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65,
|
||||||
0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73,
|
0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73,
|
||||||
0x61, 0x67, 0x65, 0x22, 0x00, 0x12, 0x3d, 0x0a, 0x08, 0x53, 0x79, 0x6e, 0x63, 0x4d, 0x65, 0x74,
|
0x61, 0x67, 0x65, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x12, 0x4c, 0x0a, 0x0c, 0x43, 0x72, 0x65,
|
||||||
0x61, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45,
|
0x61, 0x74, 0x65, 0x45, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61,
|
||||||
0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a,
|
0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64,
|
||||||
0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70,
|
0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65,
|
||||||
0x74, 0x79, 0x22, 0x00, 0x12, 0x3b, 0x0a, 0x06, 0x4c, 0x6f, 0x67, 0x6f, 0x75, 0x74, 0x12, 0x1c,
|
0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65,
|
||||||
0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72,
|
0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x12, 0x4b, 0x0a, 0x0b, 0x52, 0x65, 0x6e, 0x65, 0x77,
|
||||||
0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x11, 0x2e, 0x6d,
|
0x45, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d,
|
||||||
0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22,
|
|
||||||
0x00, 0x12, 0x47, 0x0a, 0x03, 0x4a, 0x6f, 0x62, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67,
|
|
||||||
0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d,
|
|
||||||
0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d,
|
|
||||||
0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73,
|
0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73,
|
||||||
0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x42, 0x08, 0x5a, 0x06, 0x2f, 0x70,
|
0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e,
|
||||||
0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61,
|
||||||
|
0x67, 0x65, 0x22, 0x00, 0x12, 0x4a, 0x0a, 0x0a, 0x53, 0x74, 0x6f, 0x70, 0x45, 0x78, 0x70, 0x6f,
|
||||||
|
0x73, 0x65, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e,
|
||||||
|
0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
|
||||||
|
0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e,
|
||||||
|
0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00,
|
||||||
|
0x42, 0x08, 0x5a, 0x06, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
|
||||||
|
0x6f, 0x33,
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -4686,152 +5117,166 @@ func file_management_proto_rawDescGZIP() []byte {
|
|||||||
return file_management_proto_rawDescData
|
return file_management_proto_rawDescData
|
||||||
}
|
}
|
||||||
|
|
||||||
var file_management_proto_enumTypes = make([]protoimpl.EnumInfo, 6)
|
var file_management_proto_enumTypes = make([]protoimpl.EnumInfo, 7)
|
||||||
var file_management_proto_msgTypes = make([]protoimpl.MessageInfo, 49)
|
var file_management_proto_msgTypes = make([]protoimpl.MessageInfo, 55)
|
||||||
var file_management_proto_goTypes = []interface{}{
|
var file_management_proto_goTypes = []interface{}{
|
||||||
(JobStatus)(0), // 0: management.JobStatus
|
(JobStatus)(0), // 0: management.JobStatus
|
||||||
(RuleProtocol)(0), // 1: management.RuleProtocol
|
(RuleProtocol)(0), // 1: management.RuleProtocol
|
||||||
(RuleDirection)(0), // 2: management.RuleDirection
|
(RuleDirection)(0), // 2: management.RuleDirection
|
||||||
(RuleAction)(0), // 3: management.RuleAction
|
(RuleAction)(0), // 3: management.RuleAction
|
||||||
(HostConfig_Protocol)(0), // 4: management.HostConfig.Protocol
|
(ExposeProtocol)(0), // 4: management.ExposeProtocol
|
||||||
(DeviceAuthorizationFlowProvider)(0), // 5: management.DeviceAuthorizationFlow.provider
|
(HostConfig_Protocol)(0), // 5: management.HostConfig.Protocol
|
||||||
(*EncryptedMessage)(nil), // 6: management.EncryptedMessage
|
(DeviceAuthorizationFlowProvider)(0), // 6: management.DeviceAuthorizationFlow.provider
|
||||||
(*JobRequest)(nil), // 7: management.JobRequest
|
(*EncryptedMessage)(nil), // 7: management.EncryptedMessage
|
||||||
(*JobResponse)(nil), // 8: management.JobResponse
|
(*JobRequest)(nil), // 8: management.JobRequest
|
||||||
(*BundleParameters)(nil), // 9: management.BundleParameters
|
(*JobResponse)(nil), // 9: management.JobResponse
|
||||||
(*BundleResult)(nil), // 10: management.BundleResult
|
(*BundleParameters)(nil), // 10: management.BundleParameters
|
||||||
(*SyncRequest)(nil), // 11: management.SyncRequest
|
(*BundleResult)(nil), // 11: management.BundleResult
|
||||||
(*SyncResponse)(nil), // 12: management.SyncResponse
|
(*SyncRequest)(nil), // 12: management.SyncRequest
|
||||||
(*SyncMetaRequest)(nil), // 13: management.SyncMetaRequest
|
(*SyncResponse)(nil), // 13: management.SyncResponse
|
||||||
(*LoginRequest)(nil), // 14: management.LoginRequest
|
(*SyncMetaRequest)(nil), // 14: management.SyncMetaRequest
|
||||||
(*PeerKeys)(nil), // 15: management.PeerKeys
|
(*LoginRequest)(nil), // 15: management.LoginRequest
|
||||||
(*Environment)(nil), // 16: management.Environment
|
(*PeerKeys)(nil), // 16: management.PeerKeys
|
||||||
(*File)(nil), // 17: management.File
|
(*Environment)(nil), // 17: management.Environment
|
||||||
(*Flags)(nil), // 18: management.Flags
|
(*File)(nil), // 18: management.File
|
||||||
(*PeerSystemMeta)(nil), // 19: management.PeerSystemMeta
|
(*Flags)(nil), // 19: management.Flags
|
||||||
(*LoginResponse)(nil), // 20: management.LoginResponse
|
(*PeerSystemMeta)(nil), // 20: management.PeerSystemMeta
|
||||||
(*ServerKeyResponse)(nil), // 21: management.ServerKeyResponse
|
(*LoginResponse)(nil), // 21: management.LoginResponse
|
||||||
(*Empty)(nil), // 22: management.Empty
|
(*ServerKeyResponse)(nil), // 22: management.ServerKeyResponse
|
||||||
(*NetbirdConfig)(nil), // 23: management.NetbirdConfig
|
(*Empty)(nil), // 23: management.Empty
|
||||||
(*HostConfig)(nil), // 24: management.HostConfig
|
(*NetbirdConfig)(nil), // 24: management.NetbirdConfig
|
||||||
(*RelayConfig)(nil), // 25: management.RelayConfig
|
(*HostConfig)(nil), // 25: management.HostConfig
|
||||||
(*FlowConfig)(nil), // 26: management.FlowConfig
|
(*RelayConfig)(nil), // 26: management.RelayConfig
|
||||||
(*JWTConfig)(nil), // 27: management.JWTConfig
|
(*FlowConfig)(nil), // 27: management.FlowConfig
|
||||||
(*ProtectedHostConfig)(nil), // 28: management.ProtectedHostConfig
|
(*JWTConfig)(nil), // 28: management.JWTConfig
|
||||||
(*PeerConfig)(nil), // 29: management.PeerConfig
|
(*ProtectedHostConfig)(nil), // 29: management.ProtectedHostConfig
|
||||||
(*AutoUpdateSettings)(nil), // 30: management.AutoUpdateSettings
|
(*PeerConfig)(nil), // 30: management.PeerConfig
|
||||||
(*NetworkMap)(nil), // 31: management.NetworkMap
|
(*AutoUpdateSettings)(nil), // 31: management.AutoUpdateSettings
|
||||||
(*SSHAuth)(nil), // 32: management.SSHAuth
|
(*NetworkMap)(nil), // 32: management.NetworkMap
|
||||||
(*MachineUserIndexes)(nil), // 33: management.MachineUserIndexes
|
(*SSHAuth)(nil), // 33: management.SSHAuth
|
||||||
(*RemotePeerConfig)(nil), // 34: management.RemotePeerConfig
|
(*MachineUserIndexes)(nil), // 34: management.MachineUserIndexes
|
||||||
(*SSHConfig)(nil), // 35: management.SSHConfig
|
(*RemotePeerConfig)(nil), // 35: management.RemotePeerConfig
|
||||||
(*DeviceAuthorizationFlowRequest)(nil), // 36: management.DeviceAuthorizationFlowRequest
|
(*SSHConfig)(nil), // 36: management.SSHConfig
|
||||||
(*DeviceAuthorizationFlow)(nil), // 37: management.DeviceAuthorizationFlow
|
(*DeviceAuthorizationFlowRequest)(nil), // 37: management.DeviceAuthorizationFlowRequest
|
||||||
(*PKCEAuthorizationFlowRequest)(nil), // 38: management.PKCEAuthorizationFlowRequest
|
(*DeviceAuthorizationFlow)(nil), // 38: management.DeviceAuthorizationFlow
|
||||||
(*PKCEAuthorizationFlow)(nil), // 39: management.PKCEAuthorizationFlow
|
(*PKCEAuthorizationFlowRequest)(nil), // 39: management.PKCEAuthorizationFlowRequest
|
||||||
(*ProviderConfig)(nil), // 40: management.ProviderConfig
|
(*PKCEAuthorizationFlow)(nil), // 40: management.PKCEAuthorizationFlow
|
||||||
(*Route)(nil), // 41: management.Route
|
(*ProviderConfig)(nil), // 41: management.ProviderConfig
|
||||||
(*DNSConfig)(nil), // 42: management.DNSConfig
|
(*Route)(nil), // 42: management.Route
|
||||||
(*CustomZone)(nil), // 43: management.CustomZone
|
(*DNSConfig)(nil), // 43: management.DNSConfig
|
||||||
(*SimpleRecord)(nil), // 44: management.SimpleRecord
|
(*CustomZone)(nil), // 44: management.CustomZone
|
||||||
(*NameServerGroup)(nil), // 45: management.NameServerGroup
|
(*SimpleRecord)(nil), // 45: management.SimpleRecord
|
||||||
(*NameServer)(nil), // 46: management.NameServer
|
(*NameServerGroup)(nil), // 46: management.NameServerGroup
|
||||||
(*FirewallRule)(nil), // 47: management.FirewallRule
|
(*NameServer)(nil), // 47: management.NameServer
|
||||||
(*NetworkAddress)(nil), // 48: management.NetworkAddress
|
(*FirewallRule)(nil), // 48: management.FirewallRule
|
||||||
(*Checks)(nil), // 49: management.Checks
|
(*NetworkAddress)(nil), // 49: management.NetworkAddress
|
||||||
(*PortInfo)(nil), // 50: management.PortInfo
|
(*Checks)(nil), // 50: management.Checks
|
||||||
(*RouteFirewallRule)(nil), // 51: management.RouteFirewallRule
|
(*PortInfo)(nil), // 51: management.PortInfo
|
||||||
(*ForwardingRule)(nil), // 52: management.ForwardingRule
|
(*RouteFirewallRule)(nil), // 52: management.RouteFirewallRule
|
||||||
nil, // 53: management.SSHAuth.MachineUsersEntry
|
(*ForwardingRule)(nil), // 53: management.ForwardingRule
|
||||||
(*PortInfo_Range)(nil), // 54: management.PortInfo.Range
|
(*ExposeServiceRequest)(nil), // 54: management.ExposeServiceRequest
|
||||||
(*timestamppb.Timestamp)(nil), // 55: google.protobuf.Timestamp
|
(*ExposeServiceResponse)(nil), // 55: management.ExposeServiceResponse
|
||||||
(*durationpb.Duration)(nil), // 56: google.protobuf.Duration
|
(*RenewExposeRequest)(nil), // 56: management.RenewExposeRequest
|
||||||
|
(*RenewExposeResponse)(nil), // 57: management.RenewExposeResponse
|
||||||
|
(*StopExposeRequest)(nil), // 58: management.StopExposeRequest
|
||||||
|
(*StopExposeResponse)(nil), // 59: management.StopExposeResponse
|
||||||
|
nil, // 60: management.SSHAuth.MachineUsersEntry
|
||||||
|
(*PortInfo_Range)(nil), // 61: management.PortInfo.Range
|
||||||
|
(*timestamppb.Timestamp)(nil), // 62: google.protobuf.Timestamp
|
||||||
|
(*durationpb.Duration)(nil), // 63: google.protobuf.Duration
|
||||||
}
|
}
|
||||||
var file_management_proto_depIdxs = []int32{
|
var file_management_proto_depIdxs = []int32{
|
||||||
9, // 0: management.JobRequest.bundle:type_name -> management.BundleParameters
|
10, // 0: management.JobRequest.bundle:type_name -> management.BundleParameters
|
||||||
0, // 1: management.JobResponse.status:type_name -> management.JobStatus
|
0, // 1: management.JobResponse.status:type_name -> management.JobStatus
|
||||||
10, // 2: management.JobResponse.bundle:type_name -> management.BundleResult
|
11, // 2: management.JobResponse.bundle:type_name -> management.BundleResult
|
||||||
19, // 3: management.SyncRequest.meta:type_name -> management.PeerSystemMeta
|
20, // 3: management.SyncRequest.meta:type_name -> management.PeerSystemMeta
|
||||||
23, // 4: management.SyncResponse.netbirdConfig:type_name -> management.NetbirdConfig
|
24, // 4: management.SyncResponse.netbirdConfig:type_name -> management.NetbirdConfig
|
||||||
29, // 5: management.SyncResponse.peerConfig:type_name -> management.PeerConfig
|
30, // 5: management.SyncResponse.peerConfig:type_name -> management.PeerConfig
|
||||||
34, // 6: management.SyncResponse.remotePeers:type_name -> management.RemotePeerConfig
|
35, // 6: management.SyncResponse.remotePeers:type_name -> management.RemotePeerConfig
|
||||||
31, // 7: management.SyncResponse.NetworkMap:type_name -> management.NetworkMap
|
32, // 7: management.SyncResponse.NetworkMap:type_name -> management.NetworkMap
|
||||||
49, // 8: management.SyncResponse.Checks:type_name -> management.Checks
|
50, // 8: management.SyncResponse.Checks:type_name -> management.Checks
|
||||||
19, // 9: management.SyncMetaRequest.meta:type_name -> management.PeerSystemMeta
|
20, // 9: management.SyncMetaRequest.meta:type_name -> management.PeerSystemMeta
|
||||||
19, // 10: management.LoginRequest.meta:type_name -> management.PeerSystemMeta
|
20, // 10: management.LoginRequest.meta:type_name -> management.PeerSystemMeta
|
||||||
15, // 11: management.LoginRequest.peerKeys:type_name -> management.PeerKeys
|
16, // 11: management.LoginRequest.peerKeys:type_name -> management.PeerKeys
|
||||||
48, // 12: management.PeerSystemMeta.networkAddresses:type_name -> management.NetworkAddress
|
49, // 12: management.PeerSystemMeta.networkAddresses:type_name -> management.NetworkAddress
|
||||||
16, // 13: management.PeerSystemMeta.environment:type_name -> management.Environment
|
17, // 13: management.PeerSystemMeta.environment:type_name -> management.Environment
|
||||||
17, // 14: management.PeerSystemMeta.files:type_name -> management.File
|
18, // 14: management.PeerSystemMeta.files:type_name -> management.File
|
||||||
18, // 15: management.PeerSystemMeta.flags:type_name -> management.Flags
|
19, // 15: management.PeerSystemMeta.flags:type_name -> management.Flags
|
||||||
23, // 16: management.LoginResponse.netbirdConfig:type_name -> management.NetbirdConfig
|
24, // 16: management.LoginResponse.netbirdConfig:type_name -> management.NetbirdConfig
|
||||||
29, // 17: management.LoginResponse.peerConfig:type_name -> management.PeerConfig
|
30, // 17: management.LoginResponse.peerConfig:type_name -> management.PeerConfig
|
||||||
49, // 18: management.LoginResponse.Checks:type_name -> management.Checks
|
50, // 18: management.LoginResponse.Checks:type_name -> management.Checks
|
||||||
55, // 19: management.ServerKeyResponse.expiresAt:type_name -> google.protobuf.Timestamp
|
62, // 19: management.ServerKeyResponse.expiresAt:type_name -> google.protobuf.Timestamp
|
||||||
24, // 20: management.NetbirdConfig.stuns:type_name -> management.HostConfig
|
25, // 20: management.NetbirdConfig.stuns:type_name -> management.HostConfig
|
||||||
28, // 21: management.NetbirdConfig.turns:type_name -> management.ProtectedHostConfig
|
29, // 21: management.NetbirdConfig.turns:type_name -> management.ProtectedHostConfig
|
||||||
24, // 22: management.NetbirdConfig.signal:type_name -> management.HostConfig
|
25, // 22: management.NetbirdConfig.signal:type_name -> management.HostConfig
|
||||||
25, // 23: management.NetbirdConfig.relay:type_name -> management.RelayConfig
|
26, // 23: management.NetbirdConfig.relay:type_name -> management.RelayConfig
|
||||||
26, // 24: management.NetbirdConfig.flow:type_name -> management.FlowConfig
|
27, // 24: management.NetbirdConfig.flow:type_name -> management.FlowConfig
|
||||||
4, // 25: management.HostConfig.protocol:type_name -> management.HostConfig.Protocol
|
5, // 25: management.HostConfig.protocol:type_name -> management.HostConfig.Protocol
|
||||||
56, // 26: management.FlowConfig.interval:type_name -> google.protobuf.Duration
|
63, // 26: management.FlowConfig.interval:type_name -> google.protobuf.Duration
|
||||||
24, // 27: management.ProtectedHostConfig.hostConfig:type_name -> management.HostConfig
|
25, // 27: management.ProtectedHostConfig.hostConfig:type_name -> management.HostConfig
|
||||||
35, // 28: management.PeerConfig.sshConfig:type_name -> management.SSHConfig
|
36, // 28: management.PeerConfig.sshConfig:type_name -> management.SSHConfig
|
||||||
30, // 29: management.PeerConfig.autoUpdate:type_name -> management.AutoUpdateSettings
|
31, // 29: management.PeerConfig.autoUpdate:type_name -> management.AutoUpdateSettings
|
||||||
29, // 30: management.NetworkMap.peerConfig:type_name -> management.PeerConfig
|
30, // 30: management.NetworkMap.peerConfig:type_name -> management.PeerConfig
|
||||||
34, // 31: management.NetworkMap.remotePeers:type_name -> management.RemotePeerConfig
|
35, // 31: management.NetworkMap.remotePeers:type_name -> management.RemotePeerConfig
|
||||||
41, // 32: management.NetworkMap.Routes:type_name -> management.Route
|
42, // 32: management.NetworkMap.Routes:type_name -> management.Route
|
||||||
42, // 33: management.NetworkMap.DNSConfig:type_name -> management.DNSConfig
|
43, // 33: management.NetworkMap.DNSConfig:type_name -> management.DNSConfig
|
||||||
34, // 34: management.NetworkMap.offlinePeers:type_name -> management.RemotePeerConfig
|
35, // 34: management.NetworkMap.offlinePeers:type_name -> management.RemotePeerConfig
|
||||||
47, // 35: management.NetworkMap.FirewallRules:type_name -> management.FirewallRule
|
48, // 35: management.NetworkMap.FirewallRules:type_name -> management.FirewallRule
|
||||||
51, // 36: management.NetworkMap.routesFirewallRules:type_name -> management.RouteFirewallRule
|
52, // 36: management.NetworkMap.routesFirewallRules:type_name -> management.RouteFirewallRule
|
||||||
52, // 37: management.NetworkMap.forwardingRules:type_name -> management.ForwardingRule
|
53, // 37: management.NetworkMap.forwardingRules:type_name -> management.ForwardingRule
|
||||||
32, // 38: management.NetworkMap.sshAuth:type_name -> management.SSHAuth
|
33, // 38: management.NetworkMap.sshAuth:type_name -> management.SSHAuth
|
||||||
53, // 39: management.SSHAuth.machine_users:type_name -> management.SSHAuth.MachineUsersEntry
|
60, // 39: management.SSHAuth.machine_users:type_name -> management.SSHAuth.MachineUsersEntry
|
||||||
35, // 40: management.RemotePeerConfig.sshConfig:type_name -> management.SSHConfig
|
36, // 40: management.RemotePeerConfig.sshConfig:type_name -> management.SSHConfig
|
||||||
27, // 41: management.SSHConfig.jwtConfig:type_name -> management.JWTConfig
|
28, // 41: management.SSHConfig.jwtConfig:type_name -> management.JWTConfig
|
||||||
5, // 42: management.DeviceAuthorizationFlow.Provider:type_name -> management.DeviceAuthorizationFlow.provider
|
6, // 42: management.DeviceAuthorizationFlow.Provider:type_name -> management.DeviceAuthorizationFlow.provider
|
||||||
40, // 43: management.DeviceAuthorizationFlow.ProviderConfig:type_name -> management.ProviderConfig
|
41, // 43: management.DeviceAuthorizationFlow.ProviderConfig:type_name -> management.ProviderConfig
|
||||||
40, // 44: management.PKCEAuthorizationFlow.ProviderConfig:type_name -> management.ProviderConfig
|
41, // 44: management.PKCEAuthorizationFlow.ProviderConfig:type_name -> management.ProviderConfig
|
||||||
45, // 45: management.DNSConfig.NameServerGroups:type_name -> management.NameServerGroup
|
46, // 45: management.DNSConfig.NameServerGroups:type_name -> management.NameServerGroup
|
||||||
43, // 46: management.DNSConfig.CustomZones:type_name -> management.CustomZone
|
44, // 46: management.DNSConfig.CustomZones:type_name -> management.CustomZone
|
||||||
44, // 47: management.CustomZone.Records:type_name -> management.SimpleRecord
|
45, // 47: management.CustomZone.Records:type_name -> management.SimpleRecord
|
||||||
46, // 48: management.NameServerGroup.NameServers:type_name -> management.NameServer
|
47, // 48: management.NameServerGroup.NameServers:type_name -> management.NameServer
|
||||||
2, // 49: management.FirewallRule.Direction:type_name -> management.RuleDirection
|
2, // 49: management.FirewallRule.Direction:type_name -> management.RuleDirection
|
||||||
3, // 50: management.FirewallRule.Action:type_name -> management.RuleAction
|
3, // 50: management.FirewallRule.Action:type_name -> management.RuleAction
|
||||||
1, // 51: management.FirewallRule.Protocol:type_name -> management.RuleProtocol
|
1, // 51: management.FirewallRule.Protocol:type_name -> management.RuleProtocol
|
||||||
50, // 52: management.FirewallRule.PortInfo:type_name -> management.PortInfo
|
51, // 52: management.FirewallRule.PortInfo:type_name -> management.PortInfo
|
||||||
54, // 53: management.PortInfo.range:type_name -> management.PortInfo.Range
|
61, // 53: management.PortInfo.range:type_name -> management.PortInfo.Range
|
||||||
3, // 54: management.RouteFirewallRule.action:type_name -> management.RuleAction
|
3, // 54: management.RouteFirewallRule.action:type_name -> management.RuleAction
|
||||||
1, // 55: management.RouteFirewallRule.protocol:type_name -> management.RuleProtocol
|
1, // 55: management.RouteFirewallRule.protocol:type_name -> management.RuleProtocol
|
||||||
50, // 56: management.RouteFirewallRule.portInfo:type_name -> management.PortInfo
|
51, // 56: management.RouteFirewallRule.portInfo:type_name -> management.PortInfo
|
||||||
1, // 57: management.ForwardingRule.protocol:type_name -> management.RuleProtocol
|
1, // 57: management.ForwardingRule.protocol:type_name -> management.RuleProtocol
|
||||||
50, // 58: management.ForwardingRule.destinationPort:type_name -> management.PortInfo
|
51, // 58: management.ForwardingRule.destinationPort:type_name -> management.PortInfo
|
||||||
50, // 59: management.ForwardingRule.translatedPort:type_name -> management.PortInfo
|
51, // 59: management.ForwardingRule.translatedPort:type_name -> management.PortInfo
|
||||||
33, // 60: management.SSHAuth.MachineUsersEntry.value:type_name -> management.MachineUserIndexes
|
4, // 60: management.ExposeServiceRequest.protocol:type_name -> management.ExposeProtocol
|
||||||
6, // 61: management.ManagementService.Login:input_type -> management.EncryptedMessage
|
34, // 61: management.SSHAuth.MachineUsersEntry.value:type_name -> management.MachineUserIndexes
|
||||||
6, // 62: management.ManagementService.Sync:input_type -> management.EncryptedMessage
|
7, // 62: management.ManagementService.Login:input_type -> management.EncryptedMessage
|
||||||
22, // 63: management.ManagementService.GetServerKey:input_type -> management.Empty
|
7, // 63: management.ManagementService.Sync:input_type -> management.EncryptedMessage
|
||||||
22, // 64: management.ManagementService.isHealthy:input_type -> management.Empty
|
23, // 64: management.ManagementService.GetServerKey:input_type -> management.Empty
|
||||||
6, // 65: management.ManagementService.GetDeviceAuthorizationFlow:input_type -> management.EncryptedMessage
|
23, // 65: management.ManagementService.isHealthy:input_type -> management.Empty
|
||||||
6, // 66: management.ManagementService.GetPKCEAuthorizationFlow:input_type -> management.EncryptedMessage
|
7, // 66: management.ManagementService.GetDeviceAuthorizationFlow:input_type -> management.EncryptedMessage
|
||||||
6, // 67: management.ManagementService.SyncMeta:input_type -> management.EncryptedMessage
|
7, // 67: management.ManagementService.GetPKCEAuthorizationFlow:input_type -> management.EncryptedMessage
|
||||||
6, // 68: management.ManagementService.Logout:input_type -> management.EncryptedMessage
|
7, // 68: management.ManagementService.SyncMeta:input_type -> management.EncryptedMessage
|
||||||
6, // 69: management.ManagementService.Job:input_type -> management.EncryptedMessage
|
7, // 69: management.ManagementService.Logout:input_type -> management.EncryptedMessage
|
||||||
6, // 70: management.ManagementService.Login:output_type -> management.EncryptedMessage
|
7, // 70: management.ManagementService.Job:input_type -> management.EncryptedMessage
|
||||||
6, // 71: management.ManagementService.Sync:output_type -> management.EncryptedMessage
|
7, // 71: management.ManagementService.CreateExpose:input_type -> management.EncryptedMessage
|
||||||
21, // 72: management.ManagementService.GetServerKey:output_type -> management.ServerKeyResponse
|
7, // 72: management.ManagementService.RenewExpose:input_type -> management.EncryptedMessage
|
||||||
22, // 73: management.ManagementService.isHealthy:output_type -> management.Empty
|
7, // 73: management.ManagementService.StopExpose:input_type -> management.EncryptedMessage
|
||||||
6, // 74: management.ManagementService.GetDeviceAuthorizationFlow:output_type -> management.EncryptedMessage
|
7, // 74: management.ManagementService.Login:output_type -> management.EncryptedMessage
|
||||||
6, // 75: management.ManagementService.GetPKCEAuthorizationFlow:output_type -> management.EncryptedMessage
|
7, // 75: management.ManagementService.Sync:output_type -> management.EncryptedMessage
|
||||||
22, // 76: management.ManagementService.SyncMeta:output_type -> management.Empty
|
22, // 76: management.ManagementService.GetServerKey:output_type -> management.ServerKeyResponse
|
||||||
22, // 77: management.ManagementService.Logout:output_type -> management.Empty
|
23, // 77: management.ManagementService.isHealthy:output_type -> management.Empty
|
||||||
6, // 78: management.ManagementService.Job:output_type -> management.EncryptedMessage
|
7, // 78: management.ManagementService.GetDeviceAuthorizationFlow:output_type -> management.EncryptedMessage
|
||||||
70, // [70:79] is the sub-list for method output_type
|
7, // 79: management.ManagementService.GetPKCEAuthorizationFlow:output_type -> management.EncryptedMessage
|
||||||
61, // [61:70] is the sub-list for method input_type
|
23, // 80: management.ManagementService.SyncMeta:output_type -> management.Empty
|
||||||
61, // [61:61] is the sub-list for extension type_name
|
23, // 81: management.ManagementService.Logout:output_type -> management.Empty
|
||||||
61, // [61:61] is the sub-list for extension extendee
|
7, // 82: management.ManagementService.Job:output_type -> management.EncryptedMessage
|
||||||
0, // [0:61] is the sub-list for field type_name
|
7, // 83: management.ManagementService.CreateExpose:output_type -> management.EncryptedMessage
|
||||||
|
7, // 84: management.ManagementService.RenewExpose:output_type -> management.EncryptedMessage
|
||||||
|
7, // 85: management.ManagementService.StopExpose:output_type -> management.EncryptedMessage
|
||||||
|
74, // [74:86] is the sub-list for method output_type
|
||||||
|
62, // [62:74] is the sub-list for method input_type
|
||||||
|
62, // [62:62] is the sub-list for extension type_name
|
||||||
|
62, // [62:62] is the sub-list for extension extendee
|
||||||
|
0, // [0:62] is the sub-list for field type_name
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() { file_management_proto_init() }
|
func init() { file_management_proto_init() }
|
||||||
@@ -5404,7 +5849,79 @@ func file_management_proto_init() {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
file_management_proto_msgTypes[47].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*ExposeServiceRequest); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
file_management_proto_msgTypes[48].Exporter = func(v interface{}, i int) interface{} {
|
file_management_proto_msgTypes[48].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*ExposeServiceResponse); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_management_proto_msgTypes[49].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*RenewExposeRequest); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_management_proto_msgTypes[50].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*RenewExposeResponse); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_management_proto_msgTypes[51].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*StopExposeRequest); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_management_proto_msgTypes[52].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*StopExposeResponse); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_management_proto_msgTypes[54].Exporter = func(v interface{}, i int) interface{} {
|
||||||
switch v := v.(*PortInfo_Range); i {
|
switch v := v.(*PortInfo_Range); i {
|
||||||
case 0:
|
case 0:
|
||||||
return &v.state
|
return &v.state
|
||||||
@@ -5432,8 +5949,8 @@ func file_management_proto_init() {
|
|||||||
File: protoimpl.DescBuilder{
|
File: protoimpl.DescBuilder{
|
||||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
RawDescriptor: file_management_proto_rawDesc,
|
RawDescriptor: file_management_proto_rawDesc,
|
||||||
NumEnums: 6,
|
NumEnums: 7,
|
||||||
NumMessages: 49,
|
NumMessages: 55,
|
||||||
NumExtensions: 0,
|
NumExtensions: 0,
|
||||||
NumServices: 1,
|
NumServices: 1,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -51,6 +51,15 @@ service ManagementService {
|
|||||||
|
|
||||||
// Executes a job on a target peer (e.g., debug bundle)
|
// Executes a job on a target peer (e.g., debug bundle)
|
||||||
rpc Job(stream EncryptedMessage) returns (stream EncryptedMessage) {}
|
rpc Job(stream EncryptedMessage) returns (stream EncryptedMessage) {}
|
||||||
|
|
||||||
|
// CreateExpose creates a temporary reverse proxy service for a peer
|
||||||
|
rpc CreateExpose(EncryptedMessage) returns (EncryptedMessage) {}
|
||||||
|
|
||||||
|
// RenewExpose extends the TTL of an active expose session
|
||||||
|
rpc RenewExpose(EncryptedMessage) returns (EncryptedMessage) {}
|
||||||
|
|
||||||
|
// StopExpose terminates an active expose session
|
||||||
|
rpc StopExpose(EncryptedMessage) returns (EncryptedMessage) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
message EncryptedMessage {
|
message EncryptedMessage {
|
||||||
@@ -637,3 +646,38 @@ message ForwardingRule {
|
|||||||
// Translated port information, where the traffic should be forwarded to
|
// Translated port information, where the traffic should be forwarded to
|
||||||
PortInfo translatedPort = 4;
|
PortInfo translatedPort = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum ExposeProtocol {
|
||||||
|
EXPOSE_HTTP = 0;
|
||||||
|
EXPOSE_HTTPS = 1;
|
||||||
|
EXPOSE_TCP = 2;
|
||||||
|
EXPOSE_UDP = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ExposeServiceRequest {
|
||||||
|
uint32 port = 1;
|
||||||
|
ExposeProtocol protocol = 2;
|
||||||
|
string pin = 3;
|
||||||
|
string password = 4;
|
||||||
|
repeated string user_groups = 5;
|
||||||
|
string domain = 6;
|
||||||
|
string name_prefix = 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ExposeServiceResponse {
|
||||||
|
string service_name = 1;
|
||||||
|
string service_url = 2;
|
||||||
|
string domain = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message RenewExposeRequest {
|
||||||
|
string domain = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message RenewExposeResponse {}
|
||||||
|
|
||||||
|
message StopExposeRequest {
|
||||||
|
string domain = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message StopExposeResponse {}
|
||||||
|
|||||||
@@ -52,6 +52,12 @@ type ManagementServiceClient interface {
|
|||||||
Logout(ctx context.Context, in *EncryptedMessage, opts ...grpc.CallOption) (*Empty, error)
|
Logout(ctx context.Context, in *EncryptedMessage, opts ...grpc.CallOption) (*Empty, error)
|
||||||
// Executes a job on a target peer (e.g., debug bundle)
|
// Executes a job on a target peer (e.g., debug bundle)
|
||||||
Job(ctx context.Context, opts ...grpc.CallOption) (ManagementService_JobClient, error)
|
Job(ctx context.Context, opts ...grpc.CallOption) (ManagementService_JobClient, error)
|
||||||
|
// CreateExpose creates a temporary reverse proxy service for a peer
|
||||||
|
CreateExpose(ctx context.Context, in *EncryptedMessage, opts ...grpc.CallOption) (*EncryptedMessage, error)
|
||||||
|
// RenewExpose extends the TTL of an active expose session
|
||||||
|
RenewExpose(ctx context.Context, in *EncryptedMessage, opts ...grpc.CallOption) (*EncryptedMessage, error)
|
||||||
|
// StopExpose terminates an active expose session
|
||||||
|
StopExpose(ctx context.Context, in *EncryptedMessage, opts ...grpc.CallOption) (*EncryptedMessage, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type managementServiceClient struct {
|
type managementServiceClient struct {
|
||||||
@@ -188,6 +194,33 @@ func (x *managementServiceJobClient) Recv() (*EncryptedMessage, error) {
|
|||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *managementServiceClient) CreateExpose(ctx context.Context, in *EncryptedMessage, opts ...grpc.CallOption) (*EncryptedMessage, error) {
|
||||||
|
out := new(EncryptedMessage)
|
||||||
|
err := c.cc.Invoke(ctx, "/management.ManagementService/CreateExpose", in, out, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *managementServiceClient) RenewExpose(ctx context.Context, in *EncryptedMessage, opts ...grpc.CallOption) (*EncryptedMessage, error) {
|
||||||
|
out := new(EncryptedMessage)
|
||||||
|
err := c.cc.Invoke(ctx, "/management.ManagementService/RenewExpose", in, out, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *managementServiceClient) StopExpose(ctx context.Context, in *EncryptedMessage, opts ...grpc.CallOption) (*EncryptedMessage, error) {
|
||||||
|
out := new(EncryptedMessage)
|
||||||
|
err := c.cc.Invoke(ctx, "/management.ManagementService/StopExpose", in, out, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
// ManagementServiceServer is the server API for ManagementService service.
|
// ManagementServiceServer is the server API for ManagementService service.
|
||||||
// All implementations must embed UnimplementedManagementServiceServer
|
// All implementations must embed UnimplementedManagementServiceServer
|
||||||
// for forward compatibility
|
// for forward compatibility
|
||||||
@@ -226,6 +259,12 @@ type ManagementServiceServer interface {
|
|||||||
Logout(context.Context, *EncryptedMessage) (*Empty, error)
|
Logout(context.Context, *EncryptedMessage) (*Empty, error)
|
||||||
// Executes a job on a target peer (e.g., debug bundle)
|
// Executes a job on a target peer (e.g., debug bundle)
|
||||||
Job(ManagementService_JobServer) error
|
Job(ManagementService_JobServer) error
|
||||||
|
// CreateExpose creates a temporary reverse proxy service for a peer
|
||||||
|
CreateExpose(context.Context, *EncryptedMessage) (*EncryptedMessage, error)
|
||||||
|
// RenewExpose extends the TTL of an active expose session
|
||||||
|
RenewExpose(context.Context, *EncryptedMessage) (*EncryptedMessage, error)
|
||||||
|
// StopExpose terminates an active expose session
|
||||||
|
StopExpose(context.Context, *EncryptedMessage) (*EncryptedMessage, error)
|
||||||
mustEmbedUnimplementedManagementServiceServer()
|
mustEmbedUnimplementedManagementServiceServer()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -260,6 +299,15 @@ func (UnimplementedManagementServiceServer) Logout(context.Context, *EncryptedMe
|
|||||||
func (UnimplementedManagementServiceServer) Job(ManagementService_JobServer) error {
|
func (UnimplementedManagementServiceServer) Job(ManagementService_JobServer) error {
|
||||||
return status.Errorf(codes.Unimplemented, "method Job not implemented")
|
return status.Errorf(codes.Unimplemented, "method Job not implemented")
|
||||||
}
|
}
|
||||||
|
func (UnimplementedManagementServiceServer) CreateExpose(context.Context, *EncryptedMessage) (*EncryptedMessage, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method CreateExpose not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedManagementServiceServer) RenewExpose(context.Context, *EncryptedMessage) (*EncryptedMessage, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method RenewExpose not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedManagementServiceServer) StopExpose(context.Context, *EncryptedMessage) (*EncryptedMessage, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method StopExpose not implemented")
|
||||||
|
}
|
||||||
func (UnimplementedManagementServiceServer) mustEmbedUnimplementedManagementServiceServer() {}
|
func (UnimplementedManagementServiceServer) mustEmbedUnimplementedManagementServiceServer() {}
|
||||||
|
|
||||||
// UnsafeManagementServiceServer may be embedded to opt out of forward compatibility for this service.
|
// UnsafeManagementServiceServer may be embedded to opt out of forward compatibility for this service.
|
||||||
@@ -446,6 +494,60 @@ func (x *managementServiceJobServer) Recv() (*EncryptedMessage, error) {
|
|||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func _ManagementService_CreateExpose_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(EncryptedMessage)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(ManagementServiceServer).CreateExpose(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: "/management.ManagementService/CreateExpose",
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(ManagementServiceServer).CreateExpose(ctx, req.(*EncryptedMessage))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _ManagementService_RenewExpose_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(EncryptedMessage)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(ManagementServiceServer).RenewExpose(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: "/management.ManagementService/RenewExpose",
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(ManagementServiceServer).RenewExpose(ctx, req.(*EncryptedMessage))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _ManagementService_StopExpose_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(EncryptedMessage)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(ManagementServiceServer).StopExpose(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: "/management.ManagementService/StopExpose",
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(ManagementServiceServer).StopExpose(ctx, req.(*EncryptedMessage))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
// ManagementService_ServiceDesc is the grpc.ServiceDesc for ManagementService service.
|
// ManagementService_ServiceDesc is the grpc.ServiceDesc for ManagementService service.
|
||||||
// It's only intended for direct use with grpc.RegisterService,
|
// It's only intended for direct use with grpc.RegisterService,
|
||||||
// and not to be introspected or modified (even as a copy)
|
// and not to be introspected or modified (even as a copy)
|
||||||
@@ -481,6 +583,18 @@ var ManagementService_ServiceDesc = grpc.ServiceDesc{
|
|||||||
MethodName: "Logout",
|
MethodName: "Logout",
|
||||||
Handler: _ManagementService_Logout_Handler,
|
Handler: _ManagementService_Logout_Handler,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
MethodName: "CreateExpose",
|
||||||
|
Handler: _ManagementService_CreateExpose_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "RenewExpose",
|
||||||
|
Handler: _ManagementService_RenewExpose_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "StopExpose",
|
||||||
|
Handler: _ManagementService_StopExpose_Handler,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Streams: []grpc.StreamDesc{
|
Streams: []grpc.StreamDesc{
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.26.0
|
// protoc-gen-go v1.26.0
|
||||||
// protoc v6.33.0
|
// protoc v6.33.3
|
||||||
// source: proxy_service.proto
|
// source: proxy_service.proto
|
||||||
|
|
||||||
package proto
|
package proto
|
||||||
|
|||||||
Reference in New Issue
Block a user