diff --git a/client/cmd/expose.go b/client/cmd/expose.go new file mode 100644 index 000000000..991d3ab86 --- /dev/null +++ b/client/cmd/expose.go @@ -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 ", + 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) + } + } +} diff --git a/client/cmd/root.go b/client/cmd/root.go index f4f4f6052..961abd54e 100644 --- a/client/cmd/root.go +++ b/client/cmd/root.go @@ -144,6 +144,7 @@ func init() { rootCmd.AddCommand(forwardingRulesCmd) rootCmd.AddCommand(debugCmd) rootCmd.AddCommand(profileCmd) + rootCmd.AddCommand(exposeCmd) networksCMD.AddCommand(routesListCmd) networksCMD.AddCommand(routesSelectCmd, routesDeselectCmd) diff --git a/client/internal/engine.go b/client/internal/engine.go index 90fc041a9..b0ae841f8 100644 --- a/client/internal/engine.go +++ b/client/internal/engine.go @@ -36,6 +36,7 @@ import ( "github.com/netbirdio/netbird/client/internal/dns" dnsconfig "github.com/netbirdio/netbird/client/internal/dns/config" "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/netflow" nftypes "github.com/netbirdio/netbird/client/internal/netflow/types" @@ -220,6 +221,8 @@ type Engine struct { jobExecutor *jobexec.Executor jobExecutorWG sync.WaitGroup + + exposeManager *expose.Manager } // Peer is an instance of the Connection Peer @@ -414,6 +417,7 @@ func (e *Engine) Start(netbirdConfig *mgmProto.NetbirdConfig, mgmtURL *url.URL) e.cancel() } e.ctx, e.cancel = context.WithCancel(e.clientCtx) + e.exposeManager = expose.NewManager(e.ctx, e.mgmClient) wgIface, err := e.newWgIface() if err != nil { @@ -796,7 +800,7 @@ func (e *Engine) handleAutoUpdateVersion(autoUpdateSettings *mgmProto.AutoUpdate disabled := autoUpdateSettings.Version == disableAutoUpdate - // Stop and cleanup if disabled + // stop and cleanup if disabled if e.updateManager != nil && disabled { log.Infof("auto-update is disabled, stopping update manager") e.updateManager.Stop() @@ -1818,11 +1822,18 @@ func (e *Engine) GetRouteManager() routemanager.Manager { return e.routeManager } -// GetFirewallManager returns the firewall manager +// GetFirewallManager returns the firewall manager. func (e *Engine) GetFirewallManager() firewallManager.Manager { 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) { iface, err := net.InterfaceByName(ifaceName) if err != nil { diff --git a/client/internal/expose/manager.go b/client/internal/expose/manager.go new file mode 100644 index 000000000..ba6aa6dc9 --- /dev/null +++ b/client/internal/expose/manager.go @@ -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(10 * 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) + } +} diff --git a/client/internal/expose/manager_test.go b/client/internal/expose/manager_test.go new file mode 100644 index 000000000..87d43cdb0 --- /dev/null +++ b/client/internal/expose/manager_test.go @@ -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") +} diff --git a/client/internal/expose/request.go b/client/internal/expose/request.go new file mode 100644 index 000000000..7e12d0513 --- /dev/null +++ b/client/internal/expose/request.go @@ -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, + } +} diff --git a/client/proto/daemon.pb.go b/client/proto/daemon.pb.go index 1d9d7233c..3879beba3 100644 --- a/client/proto/daemon.pb.go +++ b/client/proto/daemon.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.6 -// protoc v6.32.1 +// protoc v6.33.3 // source: daemon.proto package proto @@ -88,6 +88,58 @@ func (LogLevel) EnumDescriptor() ([]byte, []int) { 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 type OSLifecycleRequest_CycleType int32 @@ -122,11 +174,11 @@ func (x OSLifecycleRequest_CycleType) String() string { } 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 { - return &file_daemon_proto_enumTypes[1] + return &file_daemon_proto_enumTypes[2] } func (x OSLifecycleRequest_CycleType) Number() protoreflect.EnumNumber { @@ -174,11 +226,11 @@ func (x SystemEvent_Severity) String() string { } 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 { - return &file_daemon_proto_enumTypes[2] + return &file_daemon_proto_enumTypes[3] } func (x SystemEvent_Severity) Number() protoreflect.EnumNumber { @@ -229,11 +281,11 @@ func (x SystemEvent_Category) String() string { } 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 { - return &file_daemon_proto_enumTypes[3] + return &file_daemon_proto_enumTypes[4] } func (x SystemEvent_Category) Number() protoreflect.EnumNumber { @@ -5600,6 +5652,224 @@ func (x *InstallerResultResponse) GetErrorMsg() string { 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 { state protoimpl.MessageState `protogen:"open.v1"` 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() { *x = PortInfo_Range{} - mi := &file_daemon_proto_msgTypes[86] + mi := &file_daemon_proto_msgTypes[89] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5622,7 +5892,7 @@ func (x *PortInfo_Range) String() string { func (*PortInfo_Range) ProtoMessage() {} func (x *PortInfo_Range) ProtoReflect() protoreflect.Message { - mi := &file_daemon_proto_msgTypes[86] + mi := &file_daemon_proto_msgTypes[89] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6149,7 +6419,25 @@ const file_daemon_proto_rawDesc = "" + "\x16InstallerResultRequest\"O\n" + "\x17InstallerResultResponse\x12\x18\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" + "\aUNKNOWN\x10\x00\x12\t\n" + "\x05PANIC\x10\x01\x12\t\n" + @@ -6158,7 +6446,14 @@ const file_daemon_proto_rawDesc = "" + "\x04WARN\x10\x04\x12\b\n" + "\x04INFO\x10\x05\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" + "\x05Login\x12\x14.daemon.LoginRequest\x1a\x15.daemon.LoginResponse\"\x00\x12K\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" + "\x0eStopCPUProfile\x12\x1d.daemon.StopCPUProfileRequest\x1a\x1e.daemon.StopCPUProfileResponse\"\x00\x12N\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 ( file_daemon_proto_rawDescOnce sync.Once @@ -6211,214 +6507,222 @@ func file_daemon_proto_rawDescGZIP() []byte { return file_daemon_proto_rawDescData } -var file_daemon_proto_enumTypes = make([]protoimpl.EnumInfo, 4) -var file_daemon_proto_msgTypes = make([]protoimpl.MessageInfo, 88) +var file_daemon_proto_enumTypes = make([]protoimpl.EnumInfo, 5) +var file_daemon_proto_msgTypes = make([]protoimpl.MessageInfo, 91) var file_daemon_proto_goTypes = []any{ (LogLevel)(0), // 0: daemon.LogLevel - (OSLifecycleRequest_CycleType)(0), // 1: daemon.OSLifecycleRequest.CycleType - (SystemEvent_Severity)(0), // 2: daemon.SystemEvent.Severity - (SystemEvent_Category)(0), // 3: daemon.SystemEvent.Category - (*EmptyRequest)(nil), // 4: daemon.EmptyRequest - (*OSLifecycleRequest)(nil), // 5: daemon.OSLifecycleRequest - (*OSLifecycleResponse)(nil), // 6: daemon.OSLifecycleResponse - (*LoginRequest)(nil), // 7: daemon.LoginRequest - (*LoginResponse)(nil), // 8: daemon.LoginResponse - (*WaitSSOLoginRequest)(nil), // 9: daemon.WaitSSOLoginRequest - (*WaitSSOLoginResponse)(nil), // 10: daemon.WaitSSOLoginResponse - (*UpRequest)(nil), // 11: daemon.UpRequest - (*UpResponse)(nil), // 12: daemon.UpResponse - (*StatusRequest)(nil), // 13: daemon.StatusRequest - (*StatusResponse)(nil), // 14: daemon.StatusResponse - (*DownRequest)(nil), // 15: daemon.DownRequest - (*DownResponse)(nil), // 16: daemon.DownResponse - (*GetConfigRequest)(nil), // 17: daemon.GetConfigRequest - (*GetConfigResponse)(nil), // 18: daemon.GetConfigResponse - (*PeerState)(nil), // 19: daemon.PeerState - (*LocalPeerState)(nil), // 20: daemon.LocalPeerState - (*SignalState)(nil), // 21: daemon.SignalState - (*ManagementState)(nil), // 22: daemon.ManagementState - (*RelayState)(nil), // 23: daemon.RelayState - (*NSGroupState)(nil), // 24: daemon.NSGroupState - (*SSHSessionInfo)(nil), // 25: daemon.SSHSessionInfo - (*SSHServerState)(nil), // 26: daemon.SSHServerState - (*FullStatus)(nil), // 27: daemon.FullStatus - (*ListNetworksRequest)(nil), // 28: daemon.ListNetworksRequest - (*ListNetworksResponse)(nil), // 29: daemon.ListNetworksResponse - (*SelectNetworksRequest)(nil), // 30: daemon.SelectNetworksRequest - (*SelectNetworksResponse)(nil), // 31: daemon.SelectNetworksResponse - (*IPList)(nil), // 32: daemon.IPList - (*Network)(nil), // 33: daemon.Network - (*PortInfo)(nil), // 34: daemon.PortInfo - (*ForwardingRule)(nil), // 35: daemon.ForwardingRule - (*ForwardingRulesResponse)(nil), // 36: daemon.ForwardingRulesResponse - (*DebugBundleRequest)(nil), // 37: daemon.DebugBundleRequest - (*DebugBundleResponse)(nil), // 38: daemon.DebugBundleResponse - (*GetLogLevelRequest)(nil), // 39: daemon.GetLogLevelRequest - (*GetLogLevelResponse)(nil), // 40: daemon.GetLogLevelResponse - (*SetLogLevelRequest)(nil), // 41: daemon.SetLogLevelRequest - (*SetLogLevelResponse)(nil), // 42: daemon.SetLogLevelResponse - (*State)(nil), // 43: daemon.State - (*ListStatesRequest)(nil), // 44: daemon.ListStatesRequest - (*ListStatesResponse)(nil), // 45: daemon.ListStatesResponse - (*CleanStateRequest)(nil), // 46: daemon.CleanStateRequest - (*CleanStateResponse)(nil), // 47: daemon.CleanStateResponse - (*DeleteStateRequest)(nil), // 48: daemon.DeleteStateRequest - (*DeleteStateResponse)(nil), // 49: daemon.DeleteStateResponse - (*SetSyncResponsePersistenceRequest)(nil), // 50: daemon.SetSyncResponsePersistenceRequest - (*SetSyncResponsePersistenceResponse)(nil), // 51: daemon.SetSyncResponsePersistenceResponse - (*TCPFlags)(nil), // 52: daemon.TCPFlags - (*TracePacketRequest)(nil), // 53: daemon.TracePacketRequest - (*TraceStage)(nil), // 54: daemon.TraceStage - (*TracePacketResponse)(nil), // 55: daemon.TracePacketResponse - (*SubscribeRequest)(nil), // 56: daemon.SubscribeRequest - (*SystemEvent)(nil), // 57: daemon.SystemEvent - (*GetEventsRequest)(nil), // 58: daemon.GetEventsRequest - (*GetEventsResponse)(nil), // 59: daemon.GetEventsResponse - (*SwitchProfileRequest)(nil), // 60: daemon.SwitchProfileRequest - (*SwitchProfileResponse)(nil), // 61: daemon.SwitchProfileResponse - (*SetConfigRequest)(nil), // 62: daemon.SetConfigRequest - (*SetConfigResponse)(nil), // 63: daemon.SetConfigResponse - (*AddProfileRequest)(nil), // 64: daemon.AddProfileRequest - (*AddProfileResponse)(nil), // 65: daemon.AddProfileResponse - (*RemoveProfileRequest)(nil), // 66: daemon.RemoveProfileRequest - (*RemoveProfileResponse)(nil), // 67: daemon.RemoveProfileResponse - (*ListProfilesRequest)(nil), // 68: daemon.ListProfilesRequest - (*ListProfilesResponse)(nil), // 69: daemon.ListProfilesResponse - (*Profile)(nil), // 70: daemon.Profile - (*GetActiveProfileRequest)(nil), // 71: daemon.GetActiveProfileRequest - (*GetActiveProfileResponse)(nil), // 72: daemon.GetActiveProfileResponse - (*LogoutRequest)(nil), // 73: daemon.LogoutRequest - (*LogoutResponse)(nil), // 74: daemon.LogoutResponse - (*GetFeaturesRequest)(nil), // 75: daemon.GetFeaturesRequest - (*GetFeaturesResponse)(nil), // 76: daemon.GetFeaturesResponse - (*GetPeerSSHHostKeyRequest)(nil), // 77: daemon.GetPeerSSHHostKeyRequest - (*GetPeerSSHHostKeyResponse)(nil), // 78: daemon.GetPeerSSHHostKeyResponse - (*RequestJWTAuthRequest)(nil), // 79: daemon.RequestJWTAuthRequest - (*RequestJWTAuthResponse)(nil), // 80: daemon.RequestJWTAuthResponse - (*WaitJWTTokenRequest)(nil), // 81: daemon.WaitJWTTokenRequest - (*WaitJWTTokenResponse)(nil), // 82: daemon.WaitJWTTokenResponse - (*StartCPUProfileRequest)(nil), // 83: daemon.StartCPUProfileRequest - (*StartCPUProfileResponse)(nil), // 84: daemon.StartCPUProfileResponse - (*StopCPUProfileRequest)(nil), // 85: daemon.StopCPUProfileRequest - (*StopCPUProfileResponse)(nil), // 86: daemon.StopCPUProfileResponse - (*InstallerResultRequest)(nil), // 87: daemon.InstallerResultRequest - (*InstallerResultResponse)(nil), // 88: daemon.InstallerResultResponse - nil, // 89: daemon.Network.ResolvedIPsEntry - (*PortInfo_Range)(nil), // 90: daemon.PortInfo.Range - nil, // 91: daemon.SystemEvent.MetadataEntry - (*durationpb.Duration)(nil), // 92: google.protobuf.Duration - (*timestamppb.Timestamp)(nil), // 93: google.protobuf.Timestamp + (ExposeProtocol)(0), // 1: daemon.ExposeProtocol + (OSLifecycleRequest_CycleType)(0), // 2: daemon.OSLifecycleRequest.CycleType + (SystemEvent_Severity)(0), // 3: daemon.SystemEvent.Severity + (SystemEvent_Category)(0), // 4: daemon.SystemEvent.Category + (*EmptyRequest)(nil), // 5: daemon.EmptyRequest + (*OSLifecycleRequest)(nil), // 6: daemon.OSLifecycleRequest + (*OSLifecycleResponse)(nil), // 7: daemon.OSLifecycleResponse + (*LoginRequest)(nil), // 8: daemon.LoginRequest + (*LoginResponse)(nil), // 9: daemon.LoginResponse + (*WaitSSOLoginRequest)(nil), // 10: daemon.WaitSSOLoginRequest + (*WaitSSOLoginResponse)(nil), // 11: daemon.WaitSSOLoginResponse + (*UpRequest)(nil), // 12: daemon.UpRequest + (*UpResponse)(nil), // 13: daemon.UpResponse + (*StatusRequest)(nil), // 14: daemon.StatusRequest + (*StatusResponse)(nil), // 15: daemon.StatusResponse + (*DownRequest)(nil), // 16: daemon.DownRequest + (*DownResponse)(nil), // 17: daemon.DownResponse + (*GetConfigRequest)(nil), // 18: daemon.GetConfigRequest + (*GetConfigResponse)(nil), // 19: daemon.GetConfigResponse + (*PeerState)(nil), // 20: daemon.PeerState + (*LocalPeerState)(nil), // 21: daemon.LocalPeerState + (*SignalState)(nil), // 22: daemon.SignalState + (*ManagementState)(nil), // 23: daemon.ManagementState + (*RelayState)(nil), // 24: daemon.RelayState + (*NSGroupState)(nil), // 25: daemon.NSGroupState + (*SSHSessionInfo)(nil), // 26: daemon.SSHSessionInfo + (*SSHServerState)(nil), // 27: daemon.SSHServerState + (*FullStatus)(nil), // 28: daemon.FullStatus + (*ListNetworksRequest)(nil), // 29: daemon.ListNetworksRequest + (*ListNetworksResponse)(nil), // 30: daemon.ListNetworksResponse + (*SelectNetworksRequest)(nil), // 31: daemon.SelectNetworksRequest + (*SelectNetworksResponse)(nil), // 32: daemon.SelectNetworksResponse + (*IPList)(nil), // 33: daemon.IPList + (*Network)(nil), // 34: daemon.Network + (*PortInfo)(nil), // 35: daemon.PortInfo + (*ForwardingRule)(nil), // 36: daemon.ForwardingRule + (*ForwardingRulesResponse)(nil), // 37: daemon.ForwardingRulesResponse + (*DebugBundleRequest)(nil), // 38: daemon.DebugBundleRequest + (*DebugBundleResponse)(nil), // 39: daemon.DebugBundleResponse + (*GetLogLevelRequest)(nil), // 40: daemon.GetLogLevelRequest + (*GetLogLevelResponse)(nil), // 41: daemon.GetLogLevelResponse + (*SetLogLevelRequest)(nil), // 42: daemon.SetLogLevelRequest + (*SetLogLevelResponse)(nil), // 43: daemon.SetLogLevelResponse + (*State)(nil), // 44: daemon.State + (*ListStatesRequest)(nil), // 45: daemon.ListStatesRequest + (*ListStatesResponse)(nil), // 46: daemon.ListStatesResponse + (*CleanStateRequest)(nil), // 47: daemon.CleanStateRequest + (*CleanStateResponse)(nil), // 48: daemon.CleanStateResponse + (*DeleteStateRequest)(nil), // 49: daemon.DeleteStateRequest + (*DeleteStateResponse)(nil), // 50: daemon.DeleteStateResponse + (*SetSyncResponsePersistenceRequest)(nil), // 51: daemon.SetSyncResponsePersistenceRequest + (*SetSyncResponsePersistenceResponse)(nil), // 52: daemon.SetSyncResponsePersistenceResponse + (*TCPFlags)(nil), // 53: daemon.TCPFlags + (*TracePacketRequest)(nil), // 54: daemon.TracePacketRequest + (*TraceStage)(nil), // 55: daemon.TraceStage + (*TracePacketResponse)(nil), // 56: daemon.TracePacketResponse + (*SubscribeRequest)(nil), // 57: daemon.SubscribeRequest + (*SystemEvent)(nil), // 58: daemon.SystemEvent + (*GetEventsRequest)(nil), // 59: daemon.GetEventsRequest + (*GetEventsResponse)(nil), // 60: daemon.GetEventsResponse + (*SwitchProfileRequest)(nil), // 61: daemon.SwitchProfileRequest + (*SwitchProfileResponse)(nil), // 62: daemon.SwitchProfileResponse + (*SetConfigRequest)(nil), // 63: daemon.SetConfigRequest + (*SetConfigResponse)(nil), // 64: daemon.SetConfigResponse + (*AddProfileRequest)(nil), // 65: daemon.AddProfileRequest + (*AddProfileResponse)(nil), // 66: daemon.AddProfileResponse + (*RemoveProfileRequest)(nil), // 67: daemon.RemoveProfileRequest + (*RemoveProfileResponse)(nil), // 68: daemon.RemoveProfileResponse + (*ListProfilesRequest)(nil), // 69: daemon.ListProfilesRequest + (*ListProfilesResponse)(nil), // 70: daemon.ListProfilesResponse + (*Profile)(nil), // 71: daemon.Profile + (*GetActiveProfileRequest)(nil), // 72: daemon.GetActiveProfileRequest + (*GetActiveProfileResponse)(nil), // 73: daemon.GetActiveProfileResponse + (*LogoutRequest)(nil), // 74: daemon.LogoutRequest + (*LogoutResponse)(nil), // 75: daemon.LogoutResponse + (*GetFeaturesRequest)(nil), // 76: daemon.GetFeaturesRequest + (*GetFeaturesResponse)(nil), // 77: daemon.GetFeaturesResponse + (*GetPeerSSHHostKeyRequest)(nil), // 78: daemon.GetPeerSSHHostKeyRequest + (*GetPeerSSHHostKeyResponse)(nil), // 79: daemon.GetPeerSSHHostKeyResponse + (*RequestJWTAuthRequest)(nil), // 80: daemon.RequestJWTAuthRequest + (*RequestJWTAuthResponse)(nil), // 81: daemon.RequestJWTAuthResponse + (*WaitJWTTokenRequest)(nil), // 82: daemon.WaitJWTTokenRequest + (*WaitJWTTokenResponse)(nil), // 83: daemon.WaitJWTTokenResponse + (*StartCPUProfileRequest)(nil), // 84: daemon.StartCPUProfileRequest + (*StartCPUProfileResponse)(nil), // 85: daemon.StartCPUProfileResponse + (*StopCPUProfileRequest)(nil), // 86: daemon.StopCPUProfileRequest + (*StopCPUProfileResponse)(nil), // 87: daemon.StopCPUProfileResponse + (*InstallerResultRequest)(nil), // 88: daemon.InstallerResultRequest + (*InstallerResultResponse)(nil), // 89: daemon.InstallerResultResponse + (*ExposeServiceRequest)(nil), // 90: daemon.ExposeServiceRequest + (*ExposeServiceEvent)(nil), // 91: daemon.ExposeServiceEvent + (*ExposeServiceReady)(nil), // 92: daemon.ExposeServiceReady + 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{ - 1, // 0: daemon.OSLifecycleRequest.type:type_name -> daemon.OSLifecycleRequest.CycleType - 92, // 1: daemon.LoginRequest.dnsRouteInterval:type_name -> google.protobuf.Duration - 27, // 2: daemon.StatusResponse.fullStatus:type_name -> daemon.FullStatus - 93, // 3: daemon.PeerState.connStatusUpdate:type_name -> google.protobuf.Timestamp - 93, // 4: daemon.PeerState.lastWireguardHandshake:type_name -> google.protobuf.Timestamp - 92, // 5: daemon.PeerState.latency:type_name -> google.protobuf.Duration - 25, // 6: daemon.SSHServerState.sessions:type_name -> daemon.SSHSessionInfo - 22, // 7: daemon.FullStatus.managementState:type_name -> daemon.ManagementState - 21, // 8: daemon.FullStatus.signalState:type_name -> daemon.SignalState - 20, // 9: daemon.FullStatus.localPeerState:type_name -> daemon.LocalPeerState - 19, // 10: daemon.FullStatus.peers:type_name -> daemon.PeerState - 23, // 11: daemon.FullStatus.relays:type_name -> daemon.RelayState - 24, // 12: daemon.FullStatus.dns_servers:type_name -> daemon.NSGroupState - 57, // 13: daemon.FullStatus.events:type_name -> daemon.SystemEvent - 26, // 14: daemon.FullStatus.sshServerState:type_name -> daemon.SSHServerState - 33, // 15: daemon.ListNetworksResponse.routes:type_name -> daemon.Network - 89, // 16: daemon.Network.resolvedIPs:type_name -> daemon.Network.ResolvedIPsEntry - 90, // 17: daemon.PortInfo.range:type_name -> daemon.PortInfo.Range - 34, // 18: daemon.ForwardingRule.destinationPort:type_name -> daemon.PortInfo - 34, // 19: daemon.ForwardingRule.translatedPort:type_name -> daemon.PortInfo - 35, // 20: daemon.ForwardingRulesResponse.rules:type_name -> daemon.ForwardingRule + 2, // 0: daemon.OSLifecycleRequest.type:type_name -> daemon.OSLifecycleRequest.CycleType + 96, // 1: daemon.LoginRequest.dnsRouteInterval:type_name -> google.protobuf.Duration + 28, // 2: daemon.StatusResponse.fullStatus:type_name -> daemon.FullStatus + 97, // 3: daemon.PeerState.connStatusUpdate:type_name -> google.protobuf.Timestamp + 97, // 4: daemon.PeerState.lastWireguardHandshake:type_name -> google.protobuf.Timestamp + 96, // 5: daemon.PeerState.latency:type_name -> google.protobuf.Duration + 26, // 6: daemon.SSHServerState.sessions:type_name -> daemon.SSHSessionInfo + 23, // 7: daemon.FullStatus.managementState:type_name -> daemon.ManagementState + 22, // 8: daemon.FullStatus.signalState:type_name -> daemon.SignalState + 21, // 9: daemon.FullStatus.localPeerState:type_name -> daemon.LocalPeerState + 20, // 10: daemon.FullStatus.peers:type_name -> daemon.PeerState + 24, // 11: daemon.FullStatus.relays:type_name -> daemon.RelayState + 25, // 12: daemon.FullStatus.dns_servers:type_name -> daemon.NSGroupState + 58, // 13: daemon.FullStatus.events:type_name -> daemon.SystemEvent + 27, // 14: daemon.FullStatus.sshServerState:type_name -> daemon.SSHServerState + 34, // 15: daemon.ListNetworksResponse.routes:type_name -> daemon.Network + 93, // 16: daemon.Network.resolvedIPs:type_name -> daemon.Network.ResolvedIPsEntry + 94, // 17: daemon.PortInfo.range:type_name -> daemon.PortInfo.Range + 35, // 18: daemon.ForwardingRule.destinationPort:type_name -> daemon.PortInfo + 35, // 19: daemon.ForwardingRule.translatedPort:type_name -> daemon.PortInfo + 36, // 20: daemon.ForwardingRulesResponse.rules:type_name -> daemon.ForwardingRule 0, // 21: daemon.GetLogLevelResponse.level:type_name -> daemon.LogLevel 0, // 22: daemon.SetLogLevelRequest.level:type_name -> daemon.LogLevel - 43, // 23: daemon.ListStatesResponse.states:type_name -> daemon.State - 52, // 24: daemon.TracePacketRequest.tcp_flags:type_name -> daemon.TCPFlags - 54, // 25: daemon.TracePacketResponse.stages:type_name -> daemon.TraceStage - 2, // 26: daemon.SystemEvent.severity:type_name -> daemon.SystemEvent.Severity - 3, // 27: daemon.SystemEvent.category:type_name -> daemon.SystemEvent.Category - 93, // 28: daemon.SystemEvent.timestamp:type_name -> google.protobuf.Timestamp - 91, // 29: daemon.SystemEvent.metadata:type_name -> daemon.SystemEvent.MetadataEntry - 57, // 30: daemon.GetEventsResponse.events:type_name -> daemon.SystemEvent - 92, // 31: daemon.SetConfigRequest.dnsRouteInterval:type_name -> google.protobuf.Duration - 70, // 32: daemon.ListProfilesResponse.profiles:type_name -> daemon.Profile - 32, // 33: daemon.Network.ResolvedIPsEntry.value:type_name -> daemon.IPList - 7, // 34: daemon.DaemonService.Login:input_type -> daemon.LoginRequest - 9, // 35: daemon.DaemonService.WaitSSOLogin:input_type -> daemon.WaitSSOLoginRequest - 11, // 36: daemon.DaemonService.Up:input_type -> daemon.UpRequest - 13, // 37: daemon.DaemonService.Status:input_type -> daemon.StatusRequest - 15, // 38: daemon.DaemonService.Down:input_type -> daemon.DownRequest - 17, // 39: daemon.DaemonService.GetConfig:input_type -> daemon.GetConfigRequest - 28, // 40: daemon.DaemonService.ListNetworks:input_type -> daemon.ListNetworksRequest - 30, // 41: daemon.DaemonService.SelectNetworks:input_type -> daemon.SelectNetworksRequest - 30, // 42: daemon.DaemonService.DeselectNetworks:input_type -> daemon.SelectNetworksRequest - 4, // 43: daemon.DaemonService.ForwardingRules:input_type -> daemon.EmptyRequest - 37, // 44: daemon.DaemonService.DebugBundle:input_type -> daemon.DebugBundleRequest - 39, // 45: daemon.DaemonService.GetLogLevel:input_type -> daemon.GetLogLevelRequest - 41, // 46: daemon.DaemonService.SetLogLevel:input_type -> daemon.SetLogLevelRequest - 44, // 47: daemon.DaemonService.ListStates:input_type -> daemon.ListStatesRequest - 46, // 48: daemon.DaemonService.CleanState:input_type -> daemon.CleanStateRequest - 48, // 49: daemon.DaemonService.DeleteState:input_type -> daemon.DeleteStateRequest - 50, // 50: daemon.DaemonService.SetSyncResponsePersistence:input_type -> daemon.SetSyncResponsePersistenceRequest - 53, // 51: daemon.DaemonService.TracePacket:input_type -> daemon.TracePacketRequest - 56, // 52: daemon.DaemonService.SubscribeEvents:input_type -> daemon.SubscribeRequest - 58, // 53: daemon.DaemonService.GetEvents:input_type -> daemon.GetEventsRequest - 60, // 54: daemon.DaemonService.SwitchProfile:input_type -> daemon.SwitchProfileRequest - 62, // 55: daemon.DaemonService.SetConfig:input_type -> daemon.SetConfigRequest - 64, // 56: daemon.DaemonService.AddProfile:input_type -> daemon.AddProfileRequest - 66, // 57: daemon.DaemonService.RemoveProfile:input_type -> daemon.RemoveProfileRequest - 68, // 58: daemon.DaemonService.ListProfiles:input_type -> daemon.ListProfilesRequest - 71, // 59: daemon.DaemonService.GetActiveProfile:input_type -> daemon.GetActiveProfileRequest - 73, // 60: daemon.DaemonService.Logout:input_type -> daemon.LogoutRequest - 75, // 61: daemon.DaemonService.GetFeatures:input_type -> daemon.GetFeaturesRequest - 77, // 62: daemon.DaemonService.GetPeerSSHHostKey:input_type -> daemon.GetPeerSSHHostKeyRequest - 79, // 63: daemon.DaemonService.RequestJWTAuth:input_type -> daemon.RequestJWTAuthRequest - 81, // 64: daemon.DaemonService.WaitJWTToken:input_type -> daemon.WaitJWTTokenRequest - 83, // 65: daemon.DaemonService.StartCPUProfile:input_type -> daemon.StartCPUProfileRequest - 85, // 66: daemon.DaemonService.StopCPUProfile:input_type -> daemon.StopCPUProfileRequest - 5, // 67: daemon.DaemonService.NotifyOSLifecycle:input_type -> daemon.OSLifecycleRequest - 87, // 68: daemon.DaemonService.GetInstallerResult:input_type -> daemon.InstallerResultRequest - 8, // 69: daemon.DaemonService.Login:output_type -> daemon.LoginResponse - 10, // 70: daemon.DaemonService.WaitSSOLogin:output_type -> daemon.WaitSSOLoginResponse - 12, // 71: daemon.DaemonService.Up:output_type -> daemon.UpResponse - 14, // 72: daemon.DaemonService.Status:output_type -> daemon.StatusResponse - 16, // 73: daemon.DaemonService.Down:output_type -> daemon.DownResponse - 18, // 74: daemon.DaemonService.GetConfig:output_type -> daemon.GetConfigResponse - 29, // 75: daemon.DaemonService.ListNetworks:output_type -> daemon.ListNetworksResponse - 31, // 76: daemon.DaemonService.SelectNetworks:output_type -> daemon.SelectNetworksResponse - 31, // 77: daemon.DaemonService.DeselectNetworks:output_type -> daemon.SelectNetworksResponse - 36, // 78: daemon.DaemonService.ForwardingRules:output_type -> daemon.ForwardingRulesResponse - 38, // 79: daemon.DaemonService.DebugBundle:output_type -> daemon.DebugBundleResponse - 40, // 80: daemon.DaemonService.GetLogLevel:output_type -> daemon.GetLogLevelResponse - 42, // 81: daemon.DaemonService.SetLogLevel:output_type -> daemon.SetLogLevelResponse - 45, // 82: daemon.DaemonService.ListStates:output_type -> daemon.ListStatesResponse - 47, // 83: daemon.DaemonService.CleanState:output_type -> daemon.CleanStateResponse - 49, // 84: daemon.DaemonService.DeleteState:output_type -> daemon.DeleteStateResponse - 51, // 85: daemon.DaemonService.SetSyncResponsePersistence:output_type -> daemon.SetSyncResponsePersistenceResponse - 55, // 86: daemon.DaemonService.TracePacket:output_type -> daemon.TracePacketResponse - 57, // 87: daemon.DaemonService.SubscribeEvents:output_type -> daemon.SystemEvent - 59, // 88: daemon.DaemonService.GetEvents:output_type -> daemon.GetEventsResponse - 61, // 89: daemon.DaemonService.SwitchProfile:output_type -> daemon.SwitchProfileResponse - 63, // 90: daemon.DaemonService.SetConfig:output_type -> daemon.SetConfigResponse - 65, // 91: daemon.DaemonService.AddProfile:output_type -> daemon.AddProfileResponse - 67, // 92: daemon.DaemonService.RemoveProfile:output_type -> daemon.RemoveProfileResponse - 69, // 93: daemon.DaemonService.ListProfiles:output_type -> daemon.ListProfilesResponse - 72, // 94: daemon.DaemonService.GetActiveProfile:output_type -> daemon.GetActiveProfileResponse - 74, // 95: daemon.DaemonService.Logout:output_type -> daemon.LogoutResponse - 76, // 96: daemon.DaemonService.GetFeatures:output_type -> daemon.GetFeaturesResponse - 78, // 97: daemon.DaemonService.GetPeerSSHHostKey:output_type -> daemon.GetPeerSSHHostKeyResponse - 80, // 98: daemon.DaemonService.RequestJWTAuth:output_type -> daemon.RequestJWTAuthResponse - 82, // 99: daemon.DaemonService.WaitJWTToken:output_type -> daemon.WaitJWTTokenResponse - 84, // 100: daemon.DaemonService.StartCPUProfile:output_type -> daemon.StartCPUProfileResponse - 86, // 101: daemon.DaemonService.StopCPUProfile:output_type -> daemon.StopCPUProfileResponse - 6, // 102: daemon.DaemonService.NotifyOSLifecycle:output_type -> daemon.OSLifecycleResponse - 88, // 103: daemon.DaemonService.GetInstallerResult:output_type -> daemon.InstallerResultResponse - 69, // [69:104] is the sub-list for method output_type - 34, // [34:69] is the sub-list for method input_type - 34, // [34:34] is the sub-list for extension type_name - 34, // [34:34] is the sub-list for extension extendee - 0, // [0:34] is the sub-list for field type_name + 44, // 23: daemon.ListStatesResponse.states:type_name -> daemon.State + 53, // 24: daemon.TracePacketRequest.tcp_flags:type_name -> daemon.TCPFlags + 55, // 25: daemon.TracePacketResponse.stages:type_name -> daemon.TraceStage + 3, // 26: daemon.SystemEvent.severity:type_name -> daemon.SystemEvent.Severity + 4, // 27: daemon.SystemEvent.category:type_name -> daemon.SystemEvent.Category + 97, // 28: daemon.SystemEvent.timestamp:type_name -> google.protobuf.Timestamp + 95, // 29: daemon.SystemEvent.metadata:type_name -> daemon.SystemEvent.MetadataEntry + 58, // 30: daemon.GetEventsResponse.events:type_name -> daemon.SystemEvent + 96, // 31: daemon.SetConfigRequest.dnsRouteInterval:type_name -> google.protobuf.Duration + 71, // 32: daemon.ListProfilesResponse.profiles:type_name -> daemon.Profile + 1, // 33: daemon.ExposeServiceRequest.protocol:type_name -> daemon.ExposeProtocol + 92, // 34: daemon.ExposeServiceEvent.ready:type_name -> daemon.ExposeServiceReady + 33, // 35: daemon.Network.ResolvedIPsEntry.value:type_name -> daemon.IPList + 8, // 36: daemon.DaemonService.Login:input_type -> daemon.LoginRequest + 10, // 37: daemon.DaemonService.WaitSSOLogin:input_type -> daemon.WaitSSOLoginRequest + 12, // 38: daemon.DaemonService.Up:input_type -> daemon.UpRequest + 14, // 39: daemon.DaemonService.Status:input_type -> daemon.StatusRequest + 16, // 40: daemon.DaemonService.Down:input_type -> daemon.DownRequest + 18, // 41: daemon.DaemonService.GetConfig:input_type -> daemon.GetConfigRequest + 29, // 42: daemon.DaemonService.ListNetworks:input_type -> daemon.ListNetworksRequest + 31, // 43: daemon.DaemonService.SelectNetworks:input_type -> daemon.SelectNetworksRequest + 31, // 44: daemon.DaemonService.DeselectNetworks:input_type -> daemon.SelectNetworksRequest + 5, // 45: daemon.DaemonService.ForwardingRules:input_type -> daemon.EmptyRequest + 38, // 46: daemon.DaemonService.DebugBundle:input_type -> daemon.DebugBundleRequest + 40, // 47: daemon.DaemonService.GetLogLevel:input_type -> daemon.GetLogLevelRequest + 42, // 48: daemon.DaemonService.SetLogLevel:input_type -> daemon.SetLogLevelRequest + 45, // 49: daemon.DaemonService.ListStates:input_type -> daemon.ListStatesRequest + 47, // 50: daemon.DaemonService.CleanState:input_type -> daemon.CleanStateRequest + 49, // 51: daemon.DaemonService.DeleteState:input_type -> daemon.DeleteStateRequest + 51, // 52: daemon.DaemonService.SetSyncResponsePersistence:input_type -> daemon.SetSyncResponsePersistenceRequest + 54, // 53: daemon.DaemonService.TracePacket:input_type -> daemon.TracePacketRequest + 57, // 54: daemon.DaemonService.SubscribeEvents:input_type -> daemon.SubscribeRequest + 59, // 55: daemon.DaemonService.GetEvents:input_type -> daemon.GetEventsRequest + 61, // 56: daemon.DaemonService.SwitchProfile:input_type -> daemon.SwitchProfileRequest + 63, // 57: daemon.DaemonService.SetConfig:input_type -> daemon.SetConfigRequest + 65, // 58: daemon.DaemonService.AddProfile:input_type -> daemon.AddProfileRequest + 67, // 59: daemon.DaemonService.RemoveProfile:input_type -> daemon.RemoveProfileRequest + 69, // 60: daemon.DaemonService.ListProfiles:input_type -> daemon.ListProfilesRequest + 72, // 61: daemon.DaemonService.GetActiveProfile:input_type -> daemon.GetActiveProfileRequest + 74, // 62: daemon.DaemonService.Logout:input_type -> daemon.LogoutRequest + 76, // 63: daemon.DaemonService.GetFeatures:input_type -> daemon.GetFeaturesRequest + 78, // 64: daemon.DaemonService.GetPeerSSHHostKey:input_type -> daemon.GetPeerSSHHostKeyRequest + 80, // 65: daemon.DaemonService.RequestJWTAuth:input_type -> daemon.RequestJWTAuthRequest + 82, // 66: daemon.DaemonService.WaitJWTToken:input_type -> daemon.WaitJWTTokenRequest + 84, // 67: daemon.DaemonService.StartCPUProfile:input_type -> daemon.StartCPUProfileRequest + 86, // 68: daemon.DaemonService.StopCPUProfile:input_type -> daemon.StopCPUProfileRequest + 6, // 69: daemon.DaemonService.NotifyOSLifecycle:input_type -> daemon.OSLifecycleRequest + 88, // 70: daemon.DaemonService.GetInstallerResult:input_type -> daemon.InstallerResultRequest + 90, // 71: daemon.DaemonService.ExposeService:input_type -> daemon.ExposeServiceRequest + 9, // 72: daemon.DaemonService.Login:output_type -> daemon.LoginResponse + 11, // 73: daemon.DaemonService.WaitSSOLogin:output_type -> daemon.WaitSSOLoginResponse + 13, // 74: daemon.DaemonService.Up:output_type -> daemon.UpResponse + 15, // 75: daemon.DaemonService.Status:output_type -> daemon.StatusResponse + 17, // 76: daemon.DaemonService.Down:output_type -> daemon.DownResponse + 19, // 77: daemon.DaemonService.GetConfig:output_type -> daemon.GetConfigResponse + 30, // 78: daemon.DaemonService.ListNetworks:output_type -> daemon.ListNetworksResponse + 32, // 79: daemon.DaemonService.SelectNetworks:output_type -> daemon.SelectNetworksResponse + 32, // 80: daemon.DaemonService.DeselectNetworks:output_type -> daemon.SelectNetworksResponse + 37, // 81: daemon.DaemonService.ForwardingRules:output_type -> daemon.ForwardingRulesResponse + 39, // 82: daemon.DaemonService.DebugBundle:output_type -> daemon.DebugBundleResponse + 41, // 83: daemon.DaemonService.GetLogLevel:output_type -> daemon.GetLogLevelResponse + 43, // 84: daemon.DaemonService.SetLogLevel:output_type -> daemon.SetLogLevelResponse + 46, // 85: daemon.DaemonService.ListStates:output_type -> daemon.ListStatesResponse + 48, // 86: daemon.DaemonService.CleanState:output_type -> daemon.CleanStateResponse + 50, // 87: daemon.DaemonService.DeleteState:output_type -> daemon.DeleteStateResponse + 52, // 88: daemon.DaemonService.SetSyncResponsePersistence:output_type -> daemon.SetSyncResponsePersistenceResponse + 56, // 89: daemon.DaemonService.TracePacket:output_type -> daemon.TracePacketResponse + 58, // 90: daemon.DaemonService.SubscribeEvents:output_type -> daemon.SystemEvent + 60, // 91: daemon.DaemonService.GetEvents:output_type -> daemon.GetEventsResponse + 62, // 92: daemon.DaemonService.SwitchProfile:output_type -> daemon.SwitchProfileResponse + 64, // 93: daemon.DaemonService.SetConfig:output_type -> daemon.SetConfigResponse + 66, // 94: daemon.DaemonService.AddProfile:output_type -> daemon.AddProfileResponse + 68, // 95: daemon.DaemonService.RemoveProfile:output_type -> daemon.RemoveProfileResponse + 70, // 96: daemon.DaemonService.ListProfiles:output_type -> daemon.ListProfilesResponse + 73, // 97: daemon.DaemonService.GetActiveProfile:output_type -> daemon.GetActiveProfileResponse + 75, // 98: daemon.DaemonService.Logout:output_type -> daemon.LogoutResponse + 77, // 99: daemon.DaemonService.GetFeatures:output_type -> daemon.GetFeaturesResponse + 79, // 100: daemon.DaemonService.GetPeerSSHHostKey:output_type -> daemon.GetPeerSSHHostKeyResponse + 81, // 101: daemon.DaemonService.RequestJWTAuth:output_type -> daemon.RequestJWTAuthResponse + 83, // 102: daemon.DaemonService.WaitJWTToken:output_type -> daemon.WaitJWTTokenResponse + 85, // 103: daemon.DaemonService.StartCPUProfile:output_type -> daemon.StartCPUProfileResponse + 87, // 104: daemon.DaemonService.StopCPUProfile:output_type -> daemon.StopCPUProfileResponse + 7, // 105: daemon.DaemonService.NotifyOSLifecycle:output_type -> daemon.OSLifecycleResponse + 89, // 106: daemon.DaemonService.GetInstallerResult:output_type -> daemon.InstallerResultResponse + 91, // 107: daemon.DaemonService.ExposeService:output_type -> daemon.ExposeServiceEvent + 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() } @@ -6439,13 +6743,16 @@ func file_daemon_proto_init() { file_daemon_proto_msgTypes[58].OneofWrappers = []any{} file_daemon_proto_msgTypes[69].OneofWrappers = []any{} file_daemon_proto_msgTypes[75].OneofWrappers = []any{} + file_daemon_proto_msgTypes[86].OneofWrappers = []any{ + (*ExposeServiceEvent_Ready)(nil), + } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_daemon_proto_rawDesc), len(file_daemon_proto_rawDesc)), - NumEnums: 4, - NumMessages: 88, + NumEnums: 5, + NumMessages: 91, NumExtensions: 0, NumServices: 1, }, diff --git a/client/proto/daemon.proto b/client/proto/daemon.proto index 68b9a9348..4dc41d401 100644 --- a/client/proto/daemon.proto +++ b/client/proto/daemon.proto @@ -103,6 +103,9 @@ service DaemonService { rpc NotifyOSLifecycle(OSLifecycleRequest) returns(OSLifecycleResponse) {} 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; 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; +} diff --git a/client/proto/daemon_grpc.pb.go b/client/proto/daemon_grpc.pb.go index ea9b4df05..4154dce59 100644 --- a/client/proto/daemon_grpc.pb.go +++ b/client/proto/daemon_grpc.pb.go @@ -76,6 +76,8 @@ type DaemonServiceClient interface { StopCPUProfile(ctx context.Context, in *StopCPUProfileRequest, opts ...grpc.CallOption) (*StopCPUProfileResponse, error) NotifyOSLifecycle(ctx context.Context, in *OSLifecycleRequest, opts ...grpc.CallOption) (*OSLifecycleResponse, 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 { @@ -424,6 +426,38 @@ func (c *daemonServiceClient) GetInstallerResult(ctx context.Context, in *Instal 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. // All implementations must embed UnimplementedDaemonServiceServer // for forward compatibility @@ -486,6 +520,8 @@ type DaemonServiceServer interface { StopCPUProfile(context.Context, *StopCPUProfileRequest) (*StopCPUProfileResponse, error) NotifyOSLifecycle(context.Context, *OSLifecycleRequest) (*OSLifecycleResponse, error) GetInstallerResult(context.Context, *InstallerResultRequest) (*InstallerResultResponse, error) + // ExposeService exposes a local port via the NetBird reverse proxy + ExposeService(*ExposeServiceRequest, DaemonService_ExposeServiceServer) error mustEmbedUnimplementedDaemonServiceServer() } @@ -598,6 +634,9 @@ func (UnimplementedDaemonServiceServer) NotifyOSLifecycle(context.Context, *OSLi func (UnimplementedDaemonServiceServer) GetInstallerResult(context.Context, *InstallerResultRequest) (*InstallerResultResponse, error) { 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() {} // 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) } +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. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -1394,6 +1454,11 @@ var DaemonService_ServiceDesc = grpc.ServiceDesc{ Handler: _DaemonService_SubscribeEvents_Handler, ServerStreams: true, }, + { + StreamName: "ExposeService", + Handler: _DaemonService_ExposeService_Handler, + ServerStreams: true, + }, }, Metadata: "daemon.proto", } diff --git a/client/server/server.go b/client/server/server.go index 8cd057852..0466630c5 100644 --- a/client/server/server.go +++ b/client/server/server.go @@ -21,6 +21,7 @@ import ( gstatus "google.golang.org/grpc/status" "github.com/netbirdio/netbird/client/internal/auth" + "github.com/netbirdio/netbird/client/internal/expose" "github.com/netbirdio/netbird/client/internal/profilemanager" sleephandler "github.com/netbirdio/netbird/client/internal/sleep/handler" "github.com/netbirdio/netbird/client/system" @@ -1316,6 +1317,60 @@ func (s *Server) WaitJWTToken( }, 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 { if runtime.GOOS != "linux" && runtime.GOOS != "freebsd" { return false diff --git a/management/internals/modules/reverseproxy/domain/interface.go b/management/internals/modules/reverseproxy/domain/interface.go index d40e9b637..a4bba5841 100644 --- a/management/internals/modules/reverseproxy/domain/interface.go +++ b/management/internals/modules/reverseproxy/domain/interface.go @@ -9,4 +9,5 @@ type Manager interface { CreateDomain(ctx context.Context, accountID, userID, domainName, targetCluster string) (*Domain, error) DeleteDomain(ctx context.Context, accountID, userID, domainID string) error ValidateDomain(ctx context.Context, accountID, userID, domainID string) + GetClusterDomains() []string } diff --git a/management/internals/modules/reverseproxy/domain/manager/manager.go b/management/internals/modules/reverseproxy/domain/manager/manager.go index 1125f428f..55ca24ac2 100644 --- a/management/internals/modules/reverseproxy/domain/manager/manager.go +++ b/management/internals/modules/reverseproxy/domain/manager/manager.go @@ -221,6 +221,10 @@ func (m Manager) ValidateDomain(ctx context.Context, accountID, userID, domainID } } +func (m Manager) GetClusterDomains() []string { + return m.proxyURLAllowList() +} + // proxyURLAllowList retrieves a list of currently connected proxies and // their URLs func (m Manager) proxyURLAllowList() []string { diff --git a/management/internals/modules/reverseproxy/interface.go b/management/internals/modules/reverseproxy/interface.go index 8a81ee307..95402bdf7 100644 --- a/management/internals/modules/reverseproxy/interface.go +++ b/management/internals/modules/reverseproxy/interface.go @@ -21,4 +21,8 @@ type Manager interface { GetServiceByID(ctx context.Context, accountID, serviceID string) (*Service, error) GetAccountServices(ctx context.Context, accountID string) ([]*Service, error) GetServiceIDByTargetID(ctx context.Context, accountID string, resourceID string) (string, error) + ValidateExposePermission(ctx context.Context, accountID, peerID string) error + CreateServiceFromPeer(ctx context.Context, accountID, peerID string, service *Service) (*Service, error) + DeleteServiceFromPeer(ctx context.Context, accountID, peerID, serviceID string) error + ExpireServiceFromPeer(ctx context.Context, accountID, peerID, serviceID string) error } diff --git a/management/internals/modules/reverseproxy/interface_mock.go b/management/internals/modules/reverseproxy/interface_mock.go index 6533d90bf..19a4ecfe5 100644 --- a/management/internals/modules/reverseproxy/interface_mock.go +++ b/management/internals/modules/reverseproxy/interface_mock.go @@ -63,6 +63,21 @@ func (mr *MockManagerMockRecorder) DeleteAllServices(ctx, accountID, userID inte return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteAllServices", reflect.TypeOf((*MockManager)(nil).DeleteAllServices), ctx, accountID, userID) } +// CreateServiceFromPeer mocks base method. +func (m *MockManager) CreateServiceFromPeer(ctx context.Context, accountID, peerID string, service *Service) (*Service, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateServiceFromPeer", ctx, accountID, peerID, service) + ret0, _ := ret[0].(*Service) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateServiceFromPeer indicates an expected call of CreateServiceFromPeer. +func (mr *MockManagerMockRecorder) CreateServiceFromPeer(ctx, accountID, peerID, service interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateServiceFromPeer", reflect.TypeOf((*MockManager)(nil).CreateServiceFromPeer), ctx, accountID, peerID, service) +} + // DeleteService mocks base method. func (m *MockManager) DeleteService(ctx context.Context, accountID, userID, serviceID string) error { m.ctrl.T.Helper() @@ -77,6 +92,48 @@ func (mr *MockManagerMockRecorder) DeleteService(ctx, accountID, userID, service return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteService", reflect.TypeOf((*MockManager)(nil).DeleteService), ctx, accountID, userID, serviceID) } +// DeleteServiceFromPeer mocks base method. +func (m *MockManager) DeleteServiceFromPeer(ctx context.Context, accountID, peerID, serviceID string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteServiceFromPeer", ctx, accountID, peerID, serviceID) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteServiceFromPeer indicates an expected call of DeleteServiceFromPeer. +func (mr *MockManagerMockRecorder) DeleteServiceFromPeer(ctx, accountID, peerID, serviceID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteServiceFromPeer", reflect.TypeOf((*MockManager)(nil).DeleteServiceFromPeer), ctx, accountID, peerID, serviceID) +} + +// ExpireServiceFromPeer mocks base method. +func (m *MockManager) ExpireServiceFromPeer(ctx context.Context, accountID, peerID, serviceID string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ExpireServiceFromPeer", ctx, accountID, peerID, serviceID) + ret0, _ := ret[0].(error) + return ret0 +} + +// ExpireServiceFromPeer indicates an expected call of ExpireServiceFromPeer. +func (mr *MockManagerMockRecorder) ExpireServiceFromPeer(ctx, accountID, peerID, serviceID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExpireServiceFromPeer", reflect.TypeOf((*MockManager)(nil).ExpireServiceFromPeer), ctx, accountID, peerID, serviceID) +} + +// ValidateExposePermission mocks base method. +func (m *MockManager) ValidateExposePermission(ctx context.Context, accountID, peerID string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ValidateExposePermission", ctx, accountID, peerID) + ret0, _ := ret[0].(error) + return ret0 +} + +// ValidateExposePermission indicates an expected call of ValidateExposePermission. +func (mr *MockManagerMockRecorder) ValidateExposePermission(ctx, accountID, peerID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateExposePermission", reflect.TypeOf((*MockManager)(nil).ValidateExposePermission), ctx, accountID, peerID) +} + // GetAccountServices mocks base method. func (m *MockManager) GetAccountServices(ctx context.Context, accountID string) ([]*Service, error) { m.ctrl.T.Helper() diff --git a/management/internals/modules/reverseproxy/manager/manager.go b/management/internals/modules/reverseproxy/manager/manager.go index 8068178a5..ac839b8ea 100644 --- a/management/internals/modules/reverseproxy/manager/manager.go +++ b/management/internals/modules/reverseproxy/manager/manager.go @@ -3,10 +3,14 @@ package manager import ( "context" "fmt" + "math/rand/v2" "time" + nbpeer "github.com/netbirdio/netbird/management/server/peer" log "github.com/sirupsen/logrus" + "slices" + "github.com/netbirdio/netbird/management/internals/modules/reverseproxy" "github.com/netbirdio/netbird/management/internals/modules/reverseproxy/sessionkey" nbgrpc "github.com/netbirdio/netbird/management/internals/shared/grpc" @@ -15,6 +19,7 @@ import ( "github.com/netbirdio/netbird/management/server/permissions" "github.com/netbirdio/netbird/management/server/permissions/modules" "github.com/netbirdio/netbird/management/server/permissions/operations" + "github.com/netbirdio/netbird/management/server/settings" "github.com/netbirdio/netbird/management/server/store" "github.com/netbirdio/netbird/shared/management/proto" "github.com/netbirdio/netbird/shared/management/status" @@ -25,22 +30,25 @@ const unknownHostPlaceholder = "unknown" // ClusterDeriver derives the proxy cluster from a domain. type ClusterDeriver interface { DeriveClusterFromDomain(ctx context.Context, accountID, domain string) (string, error) + GetClusterDomains() []string } type managerImpl struct { store store.Store accountManager account.Manager permissionsManager permissions.Manager + settingsManager settings.Manager proxyGRPCServer *nbgrpc.ProxyServiceServer clusterDeriver ClusterDeriver } // NewManager creates a new service manager. -func NewManager(store store.Store, accountManager account.Manager, permissionsManager permissions.Manager, proxyGRPCServer *nbgrpc.ProxyServiceServer, clusterDeriver ClusterDeriver) reverseproxy.Manager { +func NewManager(store store.Store, accountManager account.Manager, permissionsManager permissions.Manager, settingsManager settings.Manager, proxyGRPCServer *nbgrpc.ProxyServiceServer, clusterDeriver ClusterDeriver) reverseproxy.Manager { return &managerImpl{ store: store, accountManager: accountManager, permissionsManager: permissionsManager, + settingsManager: settingsManager, proxyGRPCServer: proxyGRPCServer, clusterDeriver: clusterDeriver, } @@ -475,7 +483,8 @@ func (m *managerImpl) SetCertificateIssuedAt(ctx context.Context, accountID, ser 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 { return fmt.Errorf("failed to update service certificate timestamp: %w", err) @@ -607,3 +616,179 @@ func (m *managerImpl) GetServiceIDByTargetID(ctx context.Context, accountID stri 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 *managerImpl) 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 skips user permission checks since authorization is done at the gRPC handler level. +func (m *managerImpl) CreateServiceFromPeer(ctx context.Context, accountID, peerID string, service *reverseproxy.Service) (*reverseproxy.Service, error) { + service.Source = reverseproxy.SourceEphemeral + + if service.Domain == "" { + domain, err := m.buildRandomDomain(service.Name) + if err != nil { + return nil, fmt.Errorf("build random domain for service %s: %w", service.Name, err) + } + service.Domain = domain + } + + if service.Auth.BearerAuth != nil && service.Auth.BearerAuth.Enabled { + groupIDs, err := m.getGroupIDsFromNames(ctx, accountID, service.Auth.BearerAuth.DistributionGroups) + if err != nil { + return nil, fmt.Errorf("get group ids for service %s: %w", service.ID, err) + } + service.Auth.BearerAuth.DistributionGroups = groupIDs + } + + if err := m.initializeServiceForCreate(ctx, accountID, service); err != nil { + return nil, err + } + + peer, err := m.store.GetPeerByID(ctx, store.LockingStrengthNone, accountID, peerID) + if err != nil { + return nil, err + } + + now := time.Now() + service.Meta.LastRenewedAt = &now + service.SourcePeer = peerID + + if err := m.persistNewService(ctx, accountID, service); err != nil { + return nil, err + } + + meta := addPeerInfoToEventMeta(service.EventMeta(), peer) + + m.accountManager.StoreEvent(ctx, peerID, service.ID, accountID, activity.PeerServiceExposed, meta) + + if err := m.replaceHostByLookup(ctx, accountID, service); err != nil { + return nil, fmt.Errorf("replace host by lookup for service %s: %w", service.ID, err) + } + + m.sendServiceUpdate(service, reverseproxy.Create, service.ProxyCluster, "") + + m.accountManager.UpdateAccountPeers(ctx, accountID) + + return service, nil +} + +func (m *managerImpl) 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 *managerImpl) buildRandomDomain(name string) (string, error) { + 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 +} + +// DeleteServiceFromPeer deletes a peer-initiated service. +// It validates that the service was created by a peer to prevent deleting API-created services. +func (m *managerImpl) DeleteServiceFromPeer(ctx context.Context, accountID, peerID, serviceID string) error { + return m.deletePeerService(ctx, accountID, peerID, serviceID, activity.PeerServiceUnexposed) +} + +// ExpireServiceFromPeer deletes a peer-initiated service that was not renewed within the TTL. +func (m *managerImpl) ExpireServiceFromPeer(ctx context.Context, accountID, peerID, serviceID string) error { + return m.deletePeerService(ctx, accountID, peerID, serviceID, activity.PeerServiceExposeExpired) +} + +func (m *managerImpl) deletePeerService(ctx context.Context, accountID, peerID, serviceID string, activityCode activity.Activity) error { + var service *reverseproxy.Service + err := m.store.ExecuteInTransaction(ctx, func(transaction store.Store) error { + var err error + service, err = transaction.GetServiceByID(ctx, store.LockingStrengthUpdate, accountID, serviceID) + if err != nil { + return err + } + + if service.Source != reverseproxy.SourceEphemeral { + return status.Errorf(status.PermissionDenied, "cannot delete API-created service via peer expose") + } + + if service.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(service.EventMeta(), peer) + + m.accountManager.StoreEvent(ctx, peerID, serviceID, accountID, activityCode, meta) + + m.sendServiceUpdate(service, reverseproxy.Delete, service.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 +} diff --git a/management/internals/modules/reverseproxy/manager/manager_test.go b/management/internals/modules/reverseproxy/manager/manager_test.go index 266b0066f..eab853cf3 100644 --- a/management/internals/modules/reverseproxy/manager/manager_test.go +++ b/management/internals/modules/reverseproxy/manager/manager_test.go @@ -3,6 +3,7 @@ package manager import ( "context" "errors" + "net" "testing" "time" @@ -11,7 +12,16 @@ import ( "github.com/stretchr/testify/require" "github.com/netbirdio/netbird/management/internals/modules/reverseproxy" + nbgrpc "github.com/netbirdio/netbird/management/internals/shared/grpc" + "github.com/netbirdio/netbird/management/server/activity" + "github.com/netbirdio/netbird/management/server/integrations/extra_settings" + "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/settings" "github.com/netbirdio/netbird/management/server/store" + "github.com/netbirdio/netbird/management/server/types" + "github.com/netbirdio/netbird/management/server/users" "github.com/netbirdio/netbird/shared/management/status" ) @@ -356,7 +366,7 @@ func TestPreserveServiceMetadata(t *testing.T) { existing := &reverseproxy.Service{ Meta: reverseproxy.ServiceMeta{ - CertificateIssuedAt: time.Now(), + CertificateIssuedAt: func() *time.Time { t := time.Now(); return &t }(), Status: "active", }, SessionPrivateKey: "private-key", @@ -373,3 +383,516 @@ func TestPreserveServiceMetadata(t *testing.T) { assert.Equal(t, existing.SessionPrivateKey, updated.SessionPrivateKey) 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() *reverseproxy.Service { + return &reverseproxy.Service{ + ID: serviceID, + AccountID: accountID, + Name: "test-service", + Domain: "test.example.com", + Source: reverseproxy.SourceEphemeral, + SourcePeer: ownerPeerID, + } + } + + newPermanentService := func() *reverseproxy.Service { + return &reverseproxy.Service{ + ID: serviceID, + AccountID: accountID, + Name: "api-service", + Domain: "api.example.com", + Source: reverseproxy.SourcePermanent, + } + } + + newProxyServer := func(t *testing.T) *nbgrpc.ProxyServiceServer { + t.Helper() + tokenStore := nbgrpc.NewOneTimeTokenStore(1 * time.Hour) + srv := nbgrpc.NewProxyServiceServer(nil, tokenStore, nbgrpc.ProxyOIDCConfig{}, 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 := &managerImpl{ + store: mockStore, + accountManager: mockAccountMgr, + proxyGRPCServer: 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 := &managerImpl{ + 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 := &managerImpl{ + 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 := &managerImpl{ + store: mockStore, + accountManager: mockAccountMgr, + proxyGRPCServer: 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 := &managerImpl{ + store: mockStore, + accountManager: mockAccountMgr, + proxyGRPCServer: 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") + }) +} + +// noopExtraSettings is a minimal extra_settings.Manager for tests without external integrations. +type noopExtraSettings struct{} + +func (n *noopExtraSettings) GetExtraSettings(_ context.Context, _ string) (*types.ExtraSettings, error) { + return &types.ExtraSettings{}, nil +} + +func (n *noopExtraSettings) UpdateExtraSettings(_ context.Context, _, _ string, _ *types.ExtraSettings) (bool, error) { + return false, nil +} + +var _ extra_settings.Manager = (*noopExtraSettings)(nil) + +// 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) (*managerImpl, 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}, + }, + 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) + usersMgr := users.NewManager(testStore) + settingsMgr := settings.NewManager(testStore, usersMgr, &noopExtraSettings{}, permsMgr, settings.IdpConfig{}) + + var storedEvents []activity.Activity + accountMgr := &mock_server.MockAccountManager{ + StoreEventFunc: func(_ context.Context, _, _, _ string, activityID activity.ActivityDescriber, _ map[string]any) { + storedEvents = append(storedEvents, activityID.(activity.Activity)) + }, + 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 := nbgrpc.NewOneTimeTokenStore(1 * time.Hour) + proxySrv := nbgrpc.NewProxyServiceServer(nil, tokenStore, nbgrpc.ProxyOIDCConfig{}, nil, nil) + t.Cleanup(proxySrv.Close) + + mgr := &managerImpl{ + store: testStore, + accountManager: accountMgr, + permissionsManager: permsMgr, + settingsManager: settingsMgr, + proxyGRPCServer: proxySrv, + clusterDeriver: &testClusterDeriver{ + domains: []string{"test.netbird.io"}, + }, + } + + return mgr, testStore +} + +func TestValidateExposePermission(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 := &managerImpl{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) + + service := &reverseproxy.Service{ + Name: "my-expose", + Enabled: true, + Targets: []*reverseproxy.Target{ + { + AccountID: testAccountID, + Port: 8080, + Protocol: "http", + TargetId: testPeerID, + TargetType: reverseproxy.TargetTypePeer, + Enabled: true, + }, + }, + } + + created, err := mgr.CreateServiceFromPeer(ctx, testAccountID, testPeerID, service) + require.NoError(t, err) + assert.NotEmpty(t, created.ID, "service should have an ID") + assert.Contains(t, created.Domain, "test.netbird.io", "domain should use cluster domain") + assert.Equal(t, reverseproxy.SourceEphemeral, created.Source, "source should be ephemeral") + assert.Equal(t, testPeerID, created.SourcePeer, "source peer should be set") + assert.NotNil(t, created.Meta.LastRenewedAt, "last renewed should be set") + + // Verify service is persisted in store + persisted, err := testStore.GetServiceByID(ctx, store.LockingStrengthNone, testAccountID, created.ID) + require.NoError(t, err) + assert.Equal(t, created.ID, persisted.ID) + assert.Equal(t, created.Domain, persisted.Domain) + }) + + t.Run("creates service with custom domain", func(t *testing.T) { + mgr, _ := setupIntegrationTest(t) + + service := &reverseproxy.Service{ + Name: "custom", + Domain: "custom.example.com", + Enabled: true, + Targets: []*reverseproxy.Target{ + { + AccountID: testAccountID, + Port: 80, + Protocol: "http", + TargetId: testPeerID, + TargetType: reverseproxy.TargetTypePeer, + Enabled: true, + }, + }, + } + + created, err := mgr.CreateServiceFromPeer(ctx, testAccountID, testPeerID, service) + require.NoError(t, err) + assert.Equal(t, "custom.example.com", created.Domain, "should keep the provided domain") + }) + + t.Run("replaces host by peer IP lookup", func(t *testing.T) { + mgr, _ := setupIntegrationTest(t) + + service := &reverseproxy.Service{ + Name: "lookup-test", + Enabled: true, + Targets: []*reverseproxy.Target{ + { + AccountID: testAccountID, + Port: 3000, + Protocol: "http", + TargetId: testPeerID, + TargetType: reverseproxy.TargetTypePeer, + Enabled: true, + }, + }, + } + + created, err := mgr.CreateServiceFromPeer(ctx, testAccountID, testPeerID, service) + require.NoError(t, err) + require.Len(t, created.Targets, 1) + assert.Equal(t, "100.64.0.1", created.Targets[0].Host, "host should be resolved to peer IP") + }) +} + +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") + }) +} diff --git a/management/internals/modules/reverseproxy/reverseproxy.go b/management/internals/modules/reverseproxy/reverseproxy.go index 0cbbe450b..ebe9ace96 100644 --- a/management/internals/modules/reverseproxy/reverseproxy.go +++ b/management/internals/modules/reverseproxy/reverseproxy.go @@ -1,10 +1,13 @@ package reverseproxy import ( + "crypto/rand" "errors" "fmt" + "math/big" "net" "net/url" + "regexp" "strconv" "time" @@ -40,6 +43,9 @@ const ( TargetTypeHost = "host" TargetTypeDomain = "domain" TargetTypeSubnet = "subnet" + + SourcePermanent = "permanent" + SourceEphemeral = "ephemeral" ) type Target struct { @@ -114,8 +120,9 @@ type OIDCValidationConfig struct { type ServiceMeta struct { CreatedAt time.Time - CertificateIssuedAt time.Time + CertificateIssuedAt *time.Time Status string + LastRenewedAt *time.Time } type Service struct { @@ -132,6 +139,8 @@ type Service struct { Meta ServiceMeta `gorm:"embedded;embeddedPrefix:meta_"` SessionPrivateKey string `gorm:"column:session_private_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 { @@ -207,8 +216,8 @@ func (s *Service) ToAPIResponse() *api.Service { Status: api.ServiceMetaStatus(s.Meta.Status), } - if !s.Meta.CertificateIssuedAt.IsZero() { - meta.CertificateIssuedAt = &s.Meta.CertificateIssuedAt + if s.Meta.CertificateIssuedAt != nil { + meta.CertificateIssuedAt = s.Meta.CertificateIssuedAt } resp := &api.Service{ @@ -309,6 +318,63 @@ func isDefaultPort(scheme string, port int) bool { return (scheme == "https" && port == 443) || (scheme == "http" && port == 80) } +// FromExposeRequest builds a Service from a peer expose gRPC request. +func FromExposeRequest(req *proto.ExposeServiceRequest, accountID, peerID, serviceName string) *Service { + service := &Service{ + AccountID: accountID, + Name: serviceName, + Enabled: true, + Targets: []*Target{ + { + AccountID: accountID, + Port: int(req.Port), + Protocol: exposeProtocolToString(req.Protocol), + TargetId: peerID, + TargetType: TargetTypePeer, + Enabled: true, + }, + }, + } + + if req.Domain != "" { + service.Domain = serviceName + "." + req.Domain + } + + if req.Pin != "" { + service.Auth.PinAuth = &PINAuthConfig{ + Enabled: true, + Pin: req.Pin, + } + } + + if req.Password != "" { + service.Auth.PasswordAuth = &PasswordAuthConfig{ + Enabled: true, + Password: req.Password, + } + } + + if len(req.UserGroups) > 0 { + service.Auth.BearerAuth = &BearerAuthConfig{ + Enabled: true, + DistributionGroups: req.UserGroups, + } + } + + return service +} + +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" + } +} + func (s *Service) FromAPIRequest(req *api.ServiceRequest, accountID string) { s.Name = req.Name s.Domain = req.Domain @@ -403,7 +469,11 @@ func (s *Service) Validate() error { } 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 { @@ -427,6 +497,8 @@ func (s *Service) Copy() *Service { Meta: s.Meta, SessionPrivateKey: s.SessionPrivateKey, SessionPublicKey: s.SessionPublicKey, + Source: s.Source, + SourcePeer: s.SourcePeer, } } @@ -461,3 +533,43 @@ func (s *Service) DecryptSensitiveData(enc *crypt.FieldEncrypt) error { return nil } + +const alphanumCharset = "abcdefghijklmnopqrstuvwxyz0123456789" + +var validNamePrefix = regexp.MustCompile(`^[a-z0-9]([a-z0-9-]{0,30}[a-z0-9])?$`) + +// 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 +} diff --git a/management/internals/modules/reverseproxy/reverseproxy_test.go b/management/internals/modules/reverseproxy/reverseproxy_test.go index 546e80b31..c80d7e342 100644 --- a/management/internals/modules/reverseproxy/reverseproxy_test.go +++ b/management/internals/modules/reverseproxy/reverseproxy_test.go @@ -403,3 +403,146 @@ func TestAuthConfig_ClearSecrets(t *testing.T) { 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 TestFromExposeRequest(t *testing.T) { + t.Run("basic HTTP service", func(t *testing.T) { + req := &proto.ExposeServiceRequest{ + Port: 8080, + Protocol: proto.ExposeProtocol_EXPOSE_HTTP, + } + + service := FromExposeRequest(req, "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 := &proto.ExposeServiceRequest{ + Port: 3000, + Domain: "example.com", + } + + service := FromExposeRequest(req, "acc", "peer", "web") + assert.Equal(t, "web.example.com", service.Domain) + }) + + t.Run("with PIN auth", func(t *testing.T) { + req := &proto.ExposeServiceRequest{ + Port: 80, + Pin: "1234", + } + + service := FromExposeRequest(req, "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 := &proto.ExposeServiceRequest{ + Port: 80, + Password: "secret", + } + + service := FromExposeRequest(req, "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 := &proto.ExposeServiceRequest{ + Port: 80, + UserGroups: []string{"admins", "devs"}, + } + + service := FromExposeRequest(req, "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 := &proto.ExposeServiceRequest{ + Port: 443, + Domain: "myco.com", + Pin: "9999", + Password: "pass", + UserGroups: []string{"ops"}, + } + + service := FromExposeRequest(req, "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) + }) +} diff --git a/management/internals/server/boot.go b/management/internals/server/boot.go index e897a09f5..216ea0857 100644 --- a/management/internals/server/boot.go +++ b/management/internals/server/boot.go @@ -152,6 +152,8 @@ func (s *BaseServer) GRPCServer() *grpc.Server { if err != nil { log.Fatalf("failed to create management server: %v", err) } + srv.SetReverseProxyManager(s.ReverseProxyManager()) + srv.StartExposeReaper(context.Background()) mgmtProto.RegisterManagementServiceServer(gRPCAPIHandler, srv) mgmtProto.RegisterProxyServiceServer(gRPCAPIHandler, s.ReverseProxyGRPCServer()) diff --git a/management/internals/server/modules.go b/management/internals/server/modules.go index 58125c0a3..faec5b99c 100644 --- a/management/internals/server/modules.go +++ b/management/internals/server/modules.go @@ -192,7 +192,7 @@ func (s *BaseServer) RecordsManager() records.Manager { func (s *BaseServer) ReverseProxyManager() reverseproxy.Manager { return Create(s, func() reverseproxy.Manager { - return nbreverseproxy.NewManager(s.Store(), s.AccountManager(), s.PermissionsManager(), s.ReverseProxyGRPCServer(), s.ReverseProxyDomainManager()) + return nbreverseproxy.NewManager(s.Store(), s.AccountManager(), s.PermissionsManager(), s.SettingsManager(), s.ReverseProxyGRPCServer(), s.ReverseProxyDomainManager()) }) } diff --git a/management/internals/shared/grpc/expose_service.go b/management/internals/shared/grpc/expose_service.go new file mode 100644 index 000000000..45b60ceec --- /dev/null +++ b/management/internals/shared/grpc/expose_service.go @@ -0,0 +1,301 @@ +package grpc + +import ( + "context" + "regexp" + "sync" + "time" + + 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" + "github.com/netbirdio/netbird/management/internals/modules/reverseproxy" + 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" +) + +var pinRegexp = regexp.MustCompile(`^\d{6}$`) + +const ( + exposeTTL = 90 * time.Second + exposeReapInterval = 30 * time.Second + maxExposesPerPeer = 10 +) + +type activeExpose struct { + mu sync.Mutex + serviceID string + domain string + accountID string + peerID string + lastRenewed time.Time +} + +func exposeKey(peerID, domain string) string { + return peerID + ":" + domain +} + +// 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) + + if exposeReq.Protocol != proto.ExposeProtocol_EXPOSE_HTTP && exposeReq.Protocol != proto.ExposeProtocol_EXPOSE_HTTPS { + return nil, status.Errorf(codes.InvalidArgument, "only HTTP or HTTPS protocol are supported") + } + + if exposeReq.Pin != "" && !pinRegexp.MatchString(exposeReq.Pin) { + return nil, status.Errorf(codes.InvalidArgument, "invalid pin: must be exactly 6 digits") + } + + for _, g := range exposeReq.UserGroups { + if g == "" { + return nil, status.Errorf(codes.InvalidArgument, "user group name cannot be empty") + } + } + + reverseProxyMgr := s.getReverseProxyManager() + if reverseProxyMgr == nil { + return nil, status.Errorf(codes.Internal, "reverse proxy manager not available") + } + + if err := reverseProxyMgr.ValidateExposePermission(ctx, accountID, peer.ID); err != nil { + log.WithContext(ctx).Debugf("expose permission denied for peer %s: %v", peer.ID, err) + return nil, status.Errorf(codes.PermissionDenied, "permission denied") + } + + serviceName, err := reverseproxy.GenerateExposeName(exposeReq.NamePrefix) + if err != nil { + return nil, status.Errorf(codes.InvalidArgument, "generate service name: %v", err) + } + + service := reverseproxy.FromExposeRequest(exposeReq, accountID, peer.ID, serviceName) + + // Serialize the count check to prevent concurrent CreateExpose calls from + // exceeding maxExposesPerPeer. The lock is held only for the check; the + // actual service creation happens outside the lock. + s.exposeCreateMu.Lock() + if s.countPeerExposes(peer.ID) >= maxExposesPerPeer { + s.exposeCreateMu.Unlock() + return nil, status.Errorf(codes.ResourceExhausted, "peer has reached the maximum number of active expose sessions (%d)", maxExposesPerPeer) + } + s.exposeCreateMu.Unlock() + + created, err := reverseProxyMgr.CreateServiceFromPeer(ctx, accountID, peer.ID, service) + if err != nil { + log.WithContext(ctx).Errorf("failed to create service from peer: %v", err) + return nil, status.Errorf(codes.Internal, "create service: %v", err) + } + + key := exposeKey(peer.ID, created.Domain) + if _, loaded := s.activeExposes.LoadOrStore(key, &activeExpose{ + serviceID: created.ID, + domain: created.Domain, + accountID: accountID, + peerID: peer.ID, + lastRenewed: time.Now(), + }); loaded { + s.deleteExposeService(ctx, accountID, peer.ID, created) + return nil, status.Errorf(codes.AlreadyExists, "peer already has an active expose session for this domain") + } + + resp := &proto.ExposeServiceResponse{ + ServiceName: created.Name, + ServiceUrl: "https://" + created.Domain, + Domain: created.Domain, + } + + return s.encryptResponse(peerKey, resp) +} + +// 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 + } + + _, peer, err := s.authenticateExposePeer(ctx, peerKey) + if err != nil { + return nil, err + } + + key := exposeKey(peer.ID, renewReq.Domain) + val, ok := s.activeExposes.Load(key) + if !ok { + return nil, status.Errorf(codes.NotFound, "no active expose session for domain %s", renewReq.Domain) + } + + expose := val.(*activeExpose) + expose.mu.Lock() + expose.lastRenewed = time.Now() + expose.mu.Unlock() + + 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 + } + + _, peer, err := s.authenticateExposePeer(ctx, peerKey) + if err != nil { + return nil, err + } + + key := exposeKey(peer.ID, stopReq.Domain) + val, ok := s.activeExposes.LoadAndDelete(key) + if !ok { + return nil, status.Errorf(codes.NotFound, "no active expose session for domain %s", stopReq.Domain) + } + + expose := val.(*activeExpose) + s.cleanupExpose(expose, false) + + return s.encryptResponse(peerKey, &proto.StopExposeResponse{}) +} + +// StartExposeReaper starts a background goroutine that reaps expired expose sessions. +func (s *Server) StartExposeReaper(ctx context.Context) { + go func() { + ticker := time.NewTicker(exposeReapInterval) + defer ticker.Stop() + + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + s.reapExpiredExposes() + } + } + }() +} + +func (s *Server) reapExpiredExposes() { + s.activeExposes.Range(func(key, val any) bool { + expose := val.(*activeExpose) + expose.mu.Lock() + expired := time.Since(expose.lastRenewed) > exposeTTL + expose.mu.Unlock() + + if expired { + if _, deleted := s.activeExposes.LoadAndDelete(key); deleted { + log.Infof("reaping expired expose session for peer %s, domain %s", expose.peerID, expose.domain) + s.cleanupExpose(expose, true) + } + } + return true + }) +} + +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) deleteExposeService(ctx context.Context, accountID, peerID string, service *reverseproxy.Service) { + reverseProxyMgr := s.getReverseProxyManager() + if reverseProxyMgr == nil { + return + } + if err := reverseProxyMgr.DeleteServiceFromPeer(ctx, accountID, peerID, service.ID); err != nil { + log.WithContext(ctx).Debugf("failed to delete expose service %s: %v", service.ID, err) + } +} + +func (s *Server) cleanupExpose(expose *activeExpose, expired bool) { + bgCtx := context.Background() + + reverseProxyMgr := s.getReverseProxyManager() + if reverseProxyMgr == nil { + log.Errorf("cannot cleanup exposed service %s: reverse proxy manager not available", expose.serviceID) + return + } + + var err error + if expired { + err = reverseProxyMgr.ExpireServiceFromPeer(bgCtx, expose.accountID, expose.peerID, expose.serviceID) + } else { + err = reverseProxyMgr.DeleteServiceFromPeer(bgCtx, expose.accountID, expose.peerID, expose.serviceID) + } + if err != nil { + log.Errorf("failed to delete peer-exposed service %s: %v", expose.serviceID, err) + } +} + +func (s *Server) countPeerExposes(peerID string) int { + count := 0 + s.activeExposes.Range(func(_, val any) bool { + if expose := val.(*activeExpose); expose.peerID == peerID { + count++ + } + return true + }) + return count +} + +func (s *Server) getReverseProxyManager() reverseproxy.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 reverseproxy.Manager) { + s.reverseProxyMu.Lock() + defer s.reverseProxyMu.Unlock() + s.reverseProxyManager = mgr +} diff --git a/management/internals/shared/grpc/expose_service_test.go b/management/internals/shared/grpc/expose_service_test.go new file mode 100644 index 000000000..75a16ae44 --- /dev/null +++ b/management/internals/shared/grpc/expose_service_test.go @@ -0,0 +1,242 @@ +package grpc + +import ( + "sync" + "testing" + "time" + + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/netbirdio/netbird/management/internals/modules/reverseproxy" +) + +func TestPinValidation(t *testing.T) { + tests := []struct { + pin string + valid bool + }{ + {"123456", true}, + {"000000", true}, + {"12345", false}, + {"1234567", false}, + {"abcdef", false}, + {"12345a", false}, + {"", false}, + {"12 345", false}, + } + + for _, tt := range tests { + assert.Equal(t, tt.valid, pinRegexp.MatchString(tt.pin), "pin %q", tt.pin) + } +} + +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 TestCountPeerExposes(t *testing.T) { + s := &Server{} + + // No exposes + assert.Equal(t, 0, s.countPeerExposes("peer1")) + + // Add some exposes for different peers + s.activeExposes.Store("peer1:a.com", &activeExpose{peerID: "peer1"}) + s.activeExposes.Store("peer1:b.com", &activeExpose{peerID: "peer1"}) + s.activeExposes.Store("peer2:a.com", &activeExpose{peerID: "peer2"}) + + assert.Equal(t, 2, s.countPeerExposes("peer1"), "peer1 should have 2 exposes") + assert.Equal(t, 1, s.countPeerExposes("peer2"), "peer2 should have 1 expose") + assert.Equal(t, 0, s.countPeerExposes("peer3"), "peer3 should have 0 exposes") +} + +func TestReapExpiredExposes(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockMgr := reverseproxy.NewMockManager(ctrl) + + s := &Server{} + s.SetReverseProxyManager(mockMgr) + + now := time.Now() + + // Add an expired expose and a still-active one + s.activeExposes.Store("peer1:expired.com", &activeExpose{ + serviceID: "svc-expired", + domain: "expired.com", + accountID: "acct1", + peerID: "peer1", + lastRenewed: now.Add(-2 * exposeTTL), + }) + s.activeExposes.Store("peer1:active.com", &activeExpose{ + serviceID: "svc-active", + domain: "active.com", + accountID: "acct1", + peerID: "peer1", + lastRenewed: now, + }) + + // Expect ExpireServiceFromPeer called only for the expired one + mockMgr.EXPECT(). + ExpireServiceFromPeer(gomock.Any(), "acct1", "peer1", "svc-expired"). + Return(nil) + + s.reapExpiredExposes() + + // Verify expired one is removed + _, exists := s.activeExposes.Load("peer1:expired.com") + assert.False(t, exists, "expired expose should be removed") + + // Verify active one remains + _, exists = s.activeExposes.Load("peer1:active.com") + assert.True(t, exists, "active expose should remain") +} + +func TestCleanupExpose_Delete(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockMgr := reverseproxy.NewMockManager(ctrl) + + s := &Server{} + s.SetReverseProxyManager(mockMgr) + + mockMgr.EXPECT(). + DeleteServiceFromPeer(gomock.Any(), "acct1", "peer1", "svc1"). + Return(nil) + + s.cleanupExpose(&activeExpose{ + serviceID: "svc1", + accountID: "acct1", + peerID: "peer1", + }, false) +} + +func TestCleanupExpose_Expire(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockMgr := reverseproxy.NewMockManager(ctrl) + + s := &Server{} + s.SetReverseProxyManager(mockMgr) + + mockMgr.EXPECT(). + ExpireServiceFromPeer(gomock.Any(), "acct1", "peer1", "svc1"). + Return(nil) + + s.cleanupExpose(&activeExpose{ + serviceID: "svc1", + accountID: "acct1", + peerID: "peer1", + }, true) +} + +func TestCleanupExpose_NilManager(t *testing.T) { + s := &Server{} + // Should not panic when reverse proxy manager is nil + s.cleanupExpose(&activeExpose{ + serviceID: "svc1", + accountID: "acct1", + peerID: "peer1", + }, false) +} + +func TestSetReverseProxyManager(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + s := &Server{} + + // Initially nil + assert.Nil(t, s.getReverseProxyManager()) + + mockMgr := reverseproxy.NewMockManager(ctrl) + s.SetReverseProxyManager(mockMgr) + assert.NotNil(t, s.getReverseProxyManager()) + + // Can set to nil + s.SetReverseProxyManager(nil) + assert.Nil(t, s.getReverseProxyManager()) +} + +func TestReapExpiredExposes_ConcurrentSafety(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockMgr := reverseproxy.NewMockManager(ctrl) + mockMgr.EXPECT(). + ExpireServiceFromPeer(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + Return(nil). + AnyTimes() + + s := &Server{} + s.SetReverseProxyManager(mockMgr) + + // Pre-populate with expired sessions + for i := range 20 { + peerID := "peer1" + domain := "domain-" + string(rune('a'+i)) + s.activeExposes.Store(exposeKey(peerID, domain), &activeExpose{ + serviceID: "svc-" + domain, + domain: domain, + accountID: "acct1", + peerID: peerID, + lastRenewed: time.Now().Add(-2 * exposeTTL), + }) + } + + // Run reaper concurrently with count + var wg sync.WaitGroup + wg.Add(2) + go func() { + defer wg.Done() + s.reapExpiredExposes() + }() + go func() { + defer wg.Done() + s.countPeerExposes("peer1") + }() + wg.Wait() + + assert.Equal(t, 0, s.countPeerExposes("peer1"), "all expired exposes should be reaped") +} + +func TestActiveExposeMutexProtectsLastRenewed(t *testing.T) { + expose := &activeExpose{ + lastRenewed: time.Now().Add(-1 * time.Hour), + } + + // Simulate concurrent renew and read + 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() +} diff --git a/management/internals/shared/grpc/proxy_group_access_test.go b/management/internals/shared/grpc/proxy_group_access_test.go index 31b1df3b1..611ee36b6 100644 --- a/management/internals/shared/grpc/proxy_group_access_test.go +++ b/management/internals/shared/grpc/proxy_group_access_test.go @@ -76,6 +76,22 @@ func (m *mockReverseProxyManager) GetServiceIDByTargetID(_ context.Context, _, _ return "", nil } +func (m *mockReverseProxyManager) ValidateExposePermission(_ context.Context, _, _ string) error { + return nil +} + +func (m *mockReverseProxyManager) CreateServiceFromPeer(_ context.Context, _, _ string, _ *reverseproxy.Service) (*reverseproxy.Service, error) { + return &reverseproxy.Service{}, nil +} + +func (m *mockReverseProxyManager) DeleteServiceFromPeer(_ context.Context, _, _, _ string) error { + return nil +} + +func (m *mockReverseProxyManager) ExpireServiceFromPeer(_ context.Context, _, _, _ string) error { + return nil +} + type mockUsersManager struct { users map[string]*types.User err error diff --git a/management/internals/shared/grpc/server.go b/management/internals/shared/grpc/server.go index 0167aca07..3df9ce7ba 100644 --- a/management/internals/shared/grpc/server.go +++ b/management/internals/shared/grpc/server.go @@ -26,6 +26,7 @@ import ( "github.com/netbirdio/netbird/shared/management/client/common" "github.com/netbirdio/netbird/management/internals/controllers/network_map" + "github.com/netbirdio/netbird/management/internals/modules/reverseproxy" nbconfig "github.com/netbirdio/netbird/management/internals/server/config" "github.com/netbirdio/netbird/management/server/idp" "github.com/netbirdio/netbird/management/server/job" @@ -80,6 +81,11 @@ type Server struct { syncSem atomic.Int32 syncLimEnabled bool syncLim int32 + + activeExposes sync.Map + exposeCreateMu sync.Mutex + reverseProxyManager reverseproxy.Manager + reverseProxyMu sync.RWMutex } // NewServer creates a new Management server diff --git a/management/internals/shared/grpc/validate_session_test.go b/management/internals/shared/grpc/validate_session_test.go index f76d3ada0..1e03a461a 100644 --- a/management/internals/shared/grpc/validate_session_test.go +++ b/management/internals/shared/grpc/validate_session_test.go @@ -295,6 +295,22 @@ func (m *testValidateSessionProxyManager) GetServiceIDByTargetID(_ context.Conte return "", nil } +func (m *testValidateSessionProxyManager) ValidateExposePermission(_ context.Context, _, _ string) error { + return nil +} + +func (m *testValidateSessionProxyManager) CreateServiceFromPeer(_ context.Context, _, _ string, _ *reverseproxy.Service) (*reverseproxy.Service, error) { + return nil, nil +} + +func (m *testValidateSessionProxyManager) DeleteServiceFromPeer(_ context.Context, _, _, _ string) error { + return nil +} + +func (m *testValidateSessionProxyManager) ExpireServiceFromPeer(_ context.Context, _, _, _ string) error { + return nil +} + type testValidateSessionUsersManager struct { store store.Store } diff --git a/management/server/account.go b/management/server/account.go index d436445e8..fb8592164 100644 --- a/management/server/account.go +++ b/management/server/account.go @@ -376,6 +376,7 @@ func (am *DefaultAccountManager) UpdateAccountSettings(ctx context.Context, acco am.handlePeerLoginExpirationSettings(ctx, oldSettings, newSettings, userID, accountID) am.handleGroupsPropagationSettings(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 { 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 { if newSettings.PeerInactivityExpirationEnabled { if oldSettings.PeerInactivityExpiration != newSettings.PeerInactivityExpiration { diff --git a/management/server/account_test.go b/management/server/account_test.go index f9e9c162d..340e130d9 100644 --- a/management/server/account_test.go +++ b/management/server/account_test.go @@ -3124,7 +3124,7 @@ func createManager(t testing.TB) (*DefaultAccountManager, *update_channel.PeersU } 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 } diff --git a/management/server/activity/codes.go b/management/server/activity/codes.go index e1b7e5300..53cf30d4c 100644 --- a/management/server/activity/codes.go +++ b/management/server/activity/codes.go @@ -208,6 +208,18 @@ const ( ServiceUpdated Activity = 109 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 ) @@ -345,6 +357,13 @@ var activityMap = map[Activity]Code{ ServiceCreated: {"Service created", "service.create"}, ServiceUpdated: {"Service updated", "service.update"}, 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 diff --git a/management/server/http/handlers/accounts/accounts_handler.go b/management/server/http/handlers/accounts/accounts_handler.go index 122c061ce..27a57c434 100644 --- a/management/server/http/handlers/accounts/accounts_handler.go +++ b/management/server/http/handlers/accounts/accounts_handler.go @@ -168,6 +168,10 @@ func (h *handler) getAllAccounts(w http.ResponseWriter, r *http.Request) { } 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{ PeerLoginExpirationEnabled: req.Settings.PeerLoginExpirationEnabled, 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, PeerInactivityExpiration: time.Duration(float64(time.Second.Nanoseconds()) * float64(req.Settings.PeerInactivityExpiration)), + + PeerExposeEnabled: req.Settings.PeerExposeEnabled, + PeerExposeGroups: req.Settings.PeerExposeGroups, } if req.Settings.Extra != nil { @@ -336,6 +343,8 @@ func toAccountResponse(accountID string, settings *types.Settings, meta *types.A JwtAllowGroups: &jwtAllowGroups, RegularUsersViewBlocked: settings.RegularUsersViewBlocked, RoutingPeerDnsResolutionEnabled: &settings.RoutingPeerDNSResolutionEnabled, + PeerExposeEnabled: settings.PeerExposeEnabled, + PeerExposeGroups: settings.PeerExposeGroups, LazyConnectionEnabled: &settings.LazyConnectionEnabled, DnsDomain: &settings.DNSDomain, AutoUpdateVersion: &settings.AutoUpdateVersion, diff --git a/management/server/http/handlers/proxy/auth_callback_integration_test.go b/management/server/http/handlers/proxy/auth_callback_integration_test.go index 732fd57e3..77d50d818 100644 --- a/management/server/http/handlers/proxy/auth_callback_integration_test.go +++ b/management/server/http/handlers/proxy/auth_callback_integration_test.go @@ -413,6 +413,22 @@ func (m *testServiceManager) GetServiceIDByTargetID(_ context.Context, _, _ stri return "", nil } +func (m *testServiceManager) ValidateExposePermission(_ context.Context, _, _ string) error { + return nil +} + +func (m *testServiceManager) CreateServiceFromPeer(_ context.Context, _, _ string, _ *reverseproxy.Service) (*reverseproxy.Service, error) { + return nil, nil +} + +func (m *testServiceManager) DeleteServiceFromPeer(_ context.Context, _, _, _ string) error { + return nil +} + +func (m *testServiceManager) ExpireServiceFromPeer(_ context.Context, _, _, _ string) error { + return nil +} + func createTestState(t *testing.T, ps *nbgrpc.ProxyServiceServer, redirectURL string) string { t.Helper() diff --git a/management/server/http/testing/testing_tools/channel/channel.go b/management/server/http/testing/testing_tools/channel/channel.go index f5c2aafa6..fd2dc5848 100644 --- a/management/server/http/testing/testing_tools/channel/channel.go +++ b/management/server/http/testing/testing_tools/channel/channel.go @@ -94,7 +94,7 @@ func BuildApiBlackBoxWithDBState(t testing_tools.TB, sqlFile string, expectedPee proxyTokenStore := nbgrpc.NewOneTimeTokenStore(1 * time.Minute) proxyServiceServer := nbgrpc.NewProxyServiceServer(accessLogsManager, proxyTokenStore, nbgrpc.ProxyOIDCConfig{}, peersManager, userManager) domainManager := manager.NewManager(store, proxyServiceServer, permissionsManager) - reverseProxyManager := reverseproxymanager.NewManager(store, am, permissionsManager, proxyServiceServer, domainManager) + reverseProxyManager := reverseproxymanager.NewManager(store, am, permissionsManager, settingsManager, proxyServiceServer, domainManager) proxyServiceServer.SetProxyManager(reverseProxyManager) am.SetServiceManager(reverseProxyManager) diff --git a/management/server/mock_server/account_mock.go b/management/server/mock_server/account_mock.go index 032b1150f..ea848328f 100644 --- a/management/server/mock_server/account_mock.go +++ b/management/server/mock_server/account_mock.go @@ -407,7 +407,7 @@ func (am *MockAccountManager) AddPeer( // GetGroupByName mock implementation of GetGroupByName from server.AccountManager interface 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 nil, status.Errorf(codes.Unimplemented, "method GetGroupByName is not implemented") diff --git a/management/server/store/sql_store.go b/management/server/store/sql_store.go index 70d501593..e5edbae34 100644 --- a/management/server/store/sql_store.go +++ b/management/server/store/sql_store.go @@ -2114,7 +2114,8 @@ func (s *SqlStore) getServices(ctx context.Context, accountID string) ([]*revers s.Meta.CreatedAt = createdAt.Time } if certIssuedAt.Valid { - s.Meta.CertificateIssuedAt = certIssuedAt.Time + t := certIssuedAt.Time + s.Meta.CertificateIssuedAt = &t } if status.Valid { s.Meta.Status = status.String diff --git a/management/server/types/settings.go b/management/server/types/settings.go index a94e01b78..e165968fc 100644 --- a/management/server/types/settings.go +++ b/management/server/types/settings.go @@ -47,6 +47,11 @@ type Settings struct { // NetworkRange is the custom network range for that account 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 *ExtraSettings `gorm:"embedded;embeddedPrefix:extra_"` @@ -80,6 +85,8 @@ func (s *Settings) Copy() *Settings { PeerInactivityExpiration: s.PeerInactivityExpiration, RoutingPeerDNSResolutionEnabled: s.RoutingPeerDNSResolutionEnabled, + PeerExposeEnabled: s.PeerExposeEnabled, + PeerExposeGroups: slices.Clone(s.PeerExposeGroups), LazyConnectionEnabled: s.LazyConnectionEnabled, DNSDomain: s.DNSDomain, NetworkRange: s.NetworkRange, diff --git a/proxy/management_integration_test.go b/proxy/management_integration_test.go index 420194c58..12cec89ff 100644 --- a/proxy/management_integration_test.go +++ b/proxy/management_integration_test.go @@ -247,6 +247,22 @@ func (m *storeBackedServiceManager) GetServiceIDByTargetID(ctx context.Context, return "", nil } +func (m *storeBackedServiceManager) ValidateExposePermission(_ context.Context, _, _ string) error { + return nil +} + +func (m *storeBackedServiceManager) CreateServiceFromPeer(_ context.Context, _, _ string, _ *reverseproxy.Service) (*reverseproxy.Service, error) { + return &reverseproxy.Service{}, nil +} + +func (m *storeBackedServiceManager) DeleteServiceFromPeer(_ context.Context, _, _, _ string) error { + return nil +} + +func (m *storeBackedServiceManager) ExpireServiceFromPeer(_ context.Context, _, _, _ string) error { + return nil +} + func strPtr(s string) *string { return &s } diff --git a/shared/management/client/client.go b/shared/management/client/client.go index b92c636c5..ba525602e 100644 --- a/shared/management/client/client.go +++ b/shared/management/client/client.go @@ -11,6 +11,7 @@ import ( "github.com/netbirdio/netbird/shared/management/proto" ) +// Client is the interface for the management service client. type Client interface { io.Closer Sync(ctx context.Context, sysInfo *system.Info, msgHandler func(msg *proto.SyncResponse) error) error @@ -24,4 +25,7 @@ type Client interface { IsHealthy() bool SyncMeta(sysInfo *system.Info) 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 } diff --git a/shared/management/client/grpc.go b/shared/management/client/grpc.go index d54c8f870..9505b3fdf 100644 --- a/shared/management/client/grpc.go +++ b/shared/management/client/grpc.go @@ -48,6 +48,22 @@ type GrpcClient struct { 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 func NewClient(ctx context.Context, addr string, ourPrivateKey wgtypes.Key, tlsEnabled bool) (*GrpcClient, error) { var conn *grpc.ClientConn @@ -690,6 +706,123 @@ func (c *GrpcClient) Logout() error { 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 { if info == nil { return nil diff --git a/shared/management/client/mock.go b/shared/management/client/mock.go index ac96f7b36..57256d6d4 100644 --- a/shared/management/client/mock.go +++ b/shared/management/client/mock.go @@ -10,6 +10,7 @@ import ( "github.com/netbirdio/netbird/shared/management/proto" ) +// MockClient is a mock implementation of the Client interface for testing. type MockClient struct { CloseFunc func() 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 LogoutFunc func() 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 { @@ -80,10 +84,10 @@ func (m *MockClient) GetPKCEAuthorizationFlow(serverKey wgtypes.Key) (*proto.PKC if m.GetPKCEAuthorizationFlowFunc == 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) { return nil, nil } @@ -101,3 +105,24 @@ func (m *MockClient) Logout() error { } 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) +} diff --git a/shared/management/http/api/openapi.yml b/shared/management/http/api/openapi.yml index b0ce1b5cc..2927d0319 100644 --- a/shared/management/http/api/openapi.yml +++ b/shared/management/http/api/openapi.yml @@ -326,6 +326,16 @@ components: type: string format: cidr 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: $ref: '#/components/schemas/AccountExtraSettings' lazy_connection_enabled: @@ -353,6 +363,8 @@ components: - peer_inactivity_expiration_enabled - peer_inactivity_expiration - regular_users_view_blocked + - peer_expose_enabled + - peer_expose_groups AccountExtraSettings: type: object properties: diff --git a/shared/management/http/api/types.gen.go b/shared/management/http/api/types.gen.go index 7a7e75855..e53b876c2 100644 --- a/shared/management/http/api/types.gen.go +++ b/shared/management/http/api/types.gen.go @@ -512,6 +512,12 @@ type AccountSettings struct { // NetworkRange Allows to define a custom network range for the account in CIDR format 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 int `json:"peer_inactivity_expiration"` diff --git a/shared/management/proto/management.pb.go b/shared/management/proto/management.pb.go index 44838fc16..97a2a4d18 100644 --- a/shared/management/proto/management.pb.go +++ b/shared/management/proto/management.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.26.0 -// protoc v6.33.0 +// protoc v6.33.3 // source: management.proto package proto @@ -221,6 +221,58 @@ func (RuleAction) EnumDescriptor() ([]byte, []int) { 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 const ( @@ -260,11 +312,11 @@ func (x HostConfig_Protocol) String() string { } 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 { - return &file_management_proto_enumTypes[4] + return &file_management_proto_enumTypes[5] } func (x HostConfig_Protocol) Number() protoreflect.EnumNumber { @@ -303,11 +355,11 @@ func (x DeviceAuthorizationFlowProvider) String() string { } func (DeviceAuthorizationFlowProvider) Descriptor() protoreflect.EnumDescriptor { - return file_management_proto_enumTypes[5].Descriptor() + return file_management_proto_enumTypes[6].Descriptor() } func (DeviceAuthorizationFlowProvider) Type() protoreflect.EnumType { - return &file_management_proto_enumTypes[5] + return &file_management_proto_enumTypes[6] } func (x DeviceAuthorizationFlowProvider) Number() protoreflect.EnumNumber { @@ -3983,6 +4035,334 @@ func (x *ForwardingRule) GetTranslatedPort() *PortInfo { 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 { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -3995,7 +4375,7 @@ type PortInfo_Range struct { func (x *PortInfo_Range) Reset() { *x = PortInfo_Range{} if protoimpl.UnsafeEnabled { - mi := &file_management_proto_msgTypes[48] + mi := &file_management_proto_msgTypes[54] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4008,7 +4388,7 @@ func (x *PortInfo_Range) String() string { func (*PortInfo_Range) ProtoMessage() {} func (x *PortInfo_Range) ProtoReflect() protoreflect.Message { - mi := &file_management_proto_msgTypes[48] + mi := &file_management_proto_msgTypes[54] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 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, 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, - 0x74, 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, 0x32, 0x96, 0x05, 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, + 0x74, 0x22, 0xea, 0x01, 0x0a, 0x14, 0x45, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x53, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6f, + 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x36, + 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, + 0x32, 0x1a, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x78, + 0x70, 0x6f, 0x73, 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x52, 0x08, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x10, 0x0a, 0x03, 0x70, 0x69, 0x6e, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x03, 0x70, 0x69, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, + 0x77, 0x6f, 0x72, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, + 0x77, 0x6f, 0x72, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x67, 0x72, 0x6f, + 0x75, 0x70, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x75, 0x73, 0x65, 0x72, 0x47, + 0x72, 0x6f, 0x75, 0x70, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x1f, 0x0a, + 0x0b, 0x6e, 0x61, 0x6d, 0x65, 0x5f, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x18, 0x07, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0a, 0x6e, 0x61, 0x6d, 0x65, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, 0x22, 0x73, + 0x0a, 0x15, 0x45, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x69, + 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x65, + 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, 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, + 0x67, 0x65, 0x22, 0x00, 0x12, 0x3d, 0x0a, 0x08, 0x53, 0x79, 0x6e, 0x63, 0x4d, 0x65, 0x74, 0x61, + 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, 0x11, + 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, + 0x79, 0x22, 0x00, 0x12, 0x3b, 0x0a, 0x06, 0x4c, 0x6f, 0x67, 0x6f, 0x75, 0x74, 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, + 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, 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, 0x73, - 0x61, 0x67, 0x65, 0x22, 0x00, 0x12, 0x3d, 0x0a, 0x08, 0x53, 0x79, 0x6e, 0x63, 0x4d, 0x65, 0x74, - 0x61, 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, - 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70, - 0x74, 0x79, 0x22, 0x00, 0x12, 0x3b, 0x0a, 0x06, 0x4c, 0x6f, 0x67, 0x6f, 0x75, 0x74, 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, 0x11, 0x2e, 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, + 0x61, 0x67, 0x65, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x12, 0x4c, 0x0a, 0x0c, 0x43, 0x72, 0x65, + 0x61, 0x74, 0x65, 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, 0x12, 0x4b, 0x0a, 0x0b, 0x52, 0x65, 0x6e, 0x65, 0x77, + 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, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x42, 0x08, 0x5a, 0x06, 0x2f, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 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, 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 ( @@ -4686,152 +5117,166 @@ func file_management_proto_rawDescGZIP() []byte { return file_management_proto_rawDescData } -var file_management_proto_enumTypes = make([]protoimpl.EnumInfo, 6) -var file_management_proto_msgTypes = make([]protoimpl.MessageInfo, 49) +var file_management_proto_enumTypes = make([]protoimpl.EnumInfo, 7) +var file_management_proto_msgTypes = make([]protoimpl.MessageInfo, 55) var file_management_proto_goTypes = []interface{}{ (JobStatus)(0), // 0: management.JobStatus (RuleProtocol)(0), // 1: management.RuleProtocol (RuleDirection)(0), // 2: management.RuleDirection (RuleAction)(0), // 3: management.RuleAction - (HostConfig_Protocol)(0), // 4: management.HostConfig.Protocol - (DeviceAuthorizationFlowProvider)(0), // 5: management.DeviceAuthorizationFlow.provider - (*EncryptedMessage)(nil), // 6: management.EncryptedMessage - (*JobRequest)(nil), // 7: management.JobRequest - (*JobResponse)(nil), // 8: management.JobResponse - (*BundleParameters)(nil), // 9: management.BundleParameters - (*BundleResult)(nil), // 10: management.BundleResult - (*SyncRequest)(nil), // 11: management.SyncRequest - (*SyncResponse)(nil), // 12: management.SyncResponse - (*SyncMetaRequest)(nil), // 13: management.SyncMetaRequest - (*LoginRequest)(nil), // 14: management.LoginRequest - (*PeerKeys)(nil), // 15: management.PeerKeys - (*Environment)(nil), // 16: management.Environment - (*File)(nil), // 17: management.File - (*Flags)(nil), // 18: management.Flags - (*PeerSystemMeta)(nil), // 19: management.PeerSystemMeta - (*LoginResponse)(nil), // 20: management.LoginResponse - (*ServerKeyResponse)(nil), // 21: management.ServerKeyResponse - (*Empty)(nil), // 22: management.Empty - (*NetbirdConfig)(nil), // 23: management.NetbirdConfig - (*HostConfig)(nil), // 24: management.HostConfig - (*RelayConfig)(nil), // 25: management.RelayConfig - (*FlowConfig)(nil), // 26: management.FlowConfig - (*JWTConfig)(nil), // 27: management.JWTConfig - (*ProtectedHostConfig)(nil), // 28: management.ProtectedHostConfig - (*PeerConfig)(nil), // 29: management.PeerConfig - (*AutoUpdateSettings)(nil), // 30: management.AutoUpdateSettings - (*NetworkMap)(nil), // 31: management.NetworkMap - (*SSHAuth)(nil), // 32: management.SSHAuth - (*MachineUserIndexes)(nil), // 33: management.MachineUserIndexes - (*RemotePeerConfig)(nil), // 34: management.RemotePeerConfig - (*SSHConfig)(nil), // 35: management.SSHConfig - (*DeviceAuthorizationFlowRequest)(nil), // 36: management.DeviceAuthorizationFlowRequest - (*DeviceAuthorizationFlow)(nil), // 37: management.DeviceAuthorizationFlow - (*PKCEAuthorizationFlowRequest)(nil), // 38: management.PKCEAuthorizationFlowRequest - (*PKCEAuthorizationFlow)(nil), // 39: management.PKCEAuthorizationFlow - (*ProviderConfig)(nil), // 40: management.ProviderConfig - (*Route)(nil), // 41: management.Route - (*DNSConfig)(nil), // 42: management.DNSConfig - (*CustomZone)(nil), // 43: management.CustomZone - (*SimpleRecord)(nil), // 44: management.SimpleRecord - (*NameServerGroup)(nil), // 45: management.NameServerGroup - (*NameServer)(nil), // 46: management.NameServer - (*FirewallRule)(nil), // 47: management.FirewallRule - (*NetworkAddress)(nil), // 48: management.NetworkAddress - (*Checks)(nil), // 49: management.Checks - (*PortInfo)(nil), // 50: management.PortInfo - (*RouteFirewallRule)(nil), // 51: management.RouteFirewallRule - (*ForwardingRule)(nil), // 52: management.ForwardingRule - nil, // 53: management.SSHAuth.MachineUsersEntry - (*PortInfo_Range)(nil), // 54: management.PortInfo.Range - (*timestamppb.Timestamp)(nil), // 55: google.protobuf.Timestamp - (*durationpb.Duration)(nil), // 56: google.protobuf.Duration + (ExposeProtocol)(0), // 4: management.ExposeProtocol + (HostConfig_Protocol)(0), // 5: management.HostConfig.Protocol + (DeviceAuthorizationFlowProvider)(0), // 6: management.DeviceAuthorizationFlow.provider + (*EncryptedMessage)(nil), // 7: management.EncryptedMessage + (*JobRequest)(nil), // 8: management.JobRequest + (*JobResponse)(nil), // 9: management.JobResponse + (*BundleParameters)(nil), // 10: management.BundleParameters + (*BundleResult)(nil), // 11: management.BundleResult + (*SyncRequest)(nil), // 12: management.SyncRequest + (*SyncResponse)(nil), // 13: management.SyncResponse + (*SyncMetaRequest)(nil), // 14: management.SyncMetaRequest + (*LoginRequest)(nil), // 15: management.LoginRequest + (*PeerKeys)(nil), // 16: management.PeerKeys + (*Environment)(nil), // 17: management.Environment + (*File)(nil), // 18: management.File + (*Flags)(nil), // 19: management.Flags + (*PeerSystemMeta)(nil), // 20: management.PeerSystemMeta + (*LoginResponse)(nil), // 21: management.LoginResponse + (*ServerKeyResponse)(nil), // 22: management.ServerKeyResponse + (*Empty)(nil), // 23: management.Empty + (*NetbirdConfig)(nil), // 24: management.NetbirdConfig + (*HostConfig)(nil), // 25: management.HostConfig + (*RelayConfig)(nil), // 26: management.RelayConfig + (*FlowConfig)(nil), // 27: management.FlowConfig + (*JWTConfig)(nil), // 28: management.JWTConfig + (*ProtectedHostConfig)(nil), // 29: management.ProtectedHostConfig + (*PeerConfig)(nil), // 30: management.PeerConfig + (*AutoUpdateSettings)(nil), // 31: management.AutoUpdateSettings + (*NetworkMap)(nil), // 32: management.NetworkMap + (*SSHAuth)(nil), // 33: management.SSHAuth + (*MachineUserIndexes)(nil), // 34: management.MachineUserIndexes + (*RemotePeerConfig)(nil), // 35: management.RemotePeerConfig + (*SSHConfig)(nil), // 36: management.SSHConfig + (*DeviceAuthorizationFlowRequest)(nil), // 37: management.DeviceAuthorizationFlowRequest + (*DeviceAuthorizationFlow)(nil), // 38: management.DeviceAuthorizationFlow + (*PKCEAuthorizationFlowRequest)(nil), // 39: management.PKCEAuthorizationFlowRequest + (*PKCEAuthorizationFlow)(nil), // 40: management.PKCEAuthorizationFlow + (*ProviderConfig)(nil), // 41: management.ProviderConfig + (*Route)(nil), // 42: management.Route + (*DNSConfig)(nil), // 43: management.DNSConfig + (*CustomZone)(nil), // 44: management.CustomZone + (*SimpleRecord)(nil), // 45: management.SimpleRecord + (*NameServerGroup)(nil), // 46: management.NameServerGroup + (*NameServer)(nil), // 47: management.NameServer + (*FirewallRule)(nil), // 48: management.FirewallRule + (*NetworkAddress)(nil), // 49: management.NetworkAddress + (*Checks)(nil), // 50: management.Checks + (*PortInfo)(nil), // 51: management.PortInfo + (*RouteFirewallRule)(nil), // 52: management.RouteFirewallRule + (*ForwardingRule)(nil), // 53: management.ForwardingRule + (*ExposeServiceRequest)(nil), // 54: management.ExposeServiceRequest + (*ExposeServiceResponse)(nil), // 55: management.ExposeServiceResponse + (*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{ - 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 - 10, // 2: management.JobResponse.bundle:type_name -> management.BundleResult - 19, // 3: management.SyncRequest.meta:type_name -> management.PeerSystemMeta - 23, // 4: management.SyncResponse.netbirdConfig:type_name -> management.NetbirdConfig - 29, // 5: management.SyncResponse.peerConfig:type_name -> management.PeerConfig - 34, // 6: management.SyncResponse.remotePeers:type_name -> management.RemotePeerConfig - 31, // 7: management.SyncResponse.NetworkMap:type_name -> management.NetworkMap - 49, // 8: management.SyncResponse.Checks:type_name -> management.Checks - 19, // 9: management.SyncMetaRequest.meta:type_name -> management.PeerSystemMeta - 19, // 10: management.LoginRequest.meta:type_name -> management.PeerSystemMeta - 15, // 11: management.LoginRequest.peerKeys:type_name -> management.PeerKeys - 48, // 12: management.PeerSystemMeta.networkAddresses:type_name -> management.NetworkAddress - 16, // 13: management.PeerSystemMeta.environment:type_name -> management.Environment - 17, // 14: management.PeerSystemMeta.files:type_name -> management.File - 18, // 15: management.PeerSystemMeta.flags:type_name -> management.Flags - 23, // 16: management.LoginResponse.netbirdConfig:type_name -> management.NetbirdConfig - 29, // 17: management.LoginResponse.peerConfig:type_name -> management.PeerConfig - 49, // 18: management.LoginResponse.Checks:type_name -> management.Checks - 55, // 19: management.ServerKeyResponse.expiresAt:type_name -> google.protobuf.Timestamp - 24, // 20: management.NetbirdConfig.stuns:type_name -> management.HostConfig - 28, // 21: management.NetbirdConfig.turns:type_name -> management.ProtectedHostConfig - 24, // 22: management.NetbirdConfig.signal:type_name -> management.HostConfig - 25, // 23: management.NetbirdConfig.relay:type_name -> management.RelayConfig - 26, // 24: management.NetbirdConfig.flow:type_name -> management.FlowConfig - 4, // 25: management.HostConfig.protocol:type_name -> management.HostConfig.Protocol - 56, // 26: management.FlowConfig.interval:type_name -> google.protobuf.Duration - 24, // 27: management.ProtectedHostConfig.hostConfig:type_name -> management.HostConfig - 35, // 28: management.PeerConfig.sshConfig:type_name -> management.SSHConfig - 30, // 29: management.PeerConfig.autoUpdate:type_name -> management.AutoUpdateSettings - 29, // 30: management.NetworkMap.peerConfig:type_name -> management.PeerConfig - 34, // 31: management.NetworkMap.remotePeers:type_name -> management.RemotePeerConfig - 41, // 32: management.NetworkMap.Routes:type_name -> management.Route - 42, // 33: management.NetworkMap.DNSConfig:type_name -> management.DNSConfig - 34, // 34: management.NetworkMap.offlinePeers:type_name -> management.RemotePeerConfig - 47, // 35: management.NetworkMap.FirewallRules:type_name -> management.FirewallRule - 51, // 36: management.NetworkMap.routesFirewallRules:type_name -> management.RouteFirewallRule - 52, // 37: management.NetworkMap.forwardingRules:type_name -> management.ForwardingRule - 32, // 38: management.NetworkMap.sshAuth:type_name -> management.SSHAuth - 53, // 39: management.SSHAuth.machine_users:type_name -> management.SSHAuth.MachineUsersEntry - 35, // 40: management.RemotePeerConfig.sshConfig:type_name -> management.SSHConfig - 27, // 41: management.SSHConfig.jwtConfig:type_name -> management.JWTConfig - 5, // 42: management.DeviceAuthorizationFlow.Provider:type_name -> management.DeviceAuthorizationFlow.provider - 40, // 43: management.DeviceAuthorizationFlow.ProviderConfig:type_name -> management.ProviderConfig - 40, // 44: management.PKCEAuthorizationFlow.ProviderConfig:type_name -> management.ProviderConfig - 45, // 45: management.DNSConfig.NameServerGroups:type_name -> management.NameServerGroup - 43, // 46: management.DNSConfig.CustomZones:type_name -> management.CustomZone - 44, // 47: management.CustomZone.Records:type_name -> management.SimpleRecord - 46, // 48: management.NameServerGroup.NameServers:type_name -> management.NameServer + 11, // 2: management.JobResponse.bundle:type_name -> management.BundleResult + 20, // 3: management.SyncRequest.meta:type_name -> management.PeerSystemMeta + 24, // 4: management.SyncResponse.netbirdConfig:type_name -> management.NetbirdConfig + 30, // 5: management.SyncResponse.peerConfig:type_name -> management.PeerConfig + 35, // 6: management.SyncResponse.remotePeers:type_name -> management.RemotePeerConfig + 32, // 7: management.SyncResponse.NetworkMap:type_name -> management.NetworkMap + 50, // 8: management.SyncResponse.Checks:type_name -> management.Checks + 20, // 9: management.SyncMetaRequest.meta:type_name -> management.PeerSystemMeta + 20, // 10: management.LoginRequest.meta:type_name -> management.PeerSystemMeta + 16, // 11: management.LoginRequest.peerKeys:type_name -> management.PeerKeys + 49, // 12: management.PeerSystemMeta.networkAddresses:type_name -> management.NetworkAddress + 17, // 13: management.PeerSystemMeta.environment:type_name -> management.Environment + 18, // 14: management.PeerSystemMeta.files:type_name -> management.File + 19, // 15: management.PeerSystemMeta.flags:type_name -> management.Flags + 24, // 16: management.LoginResponse.netbirdConfig:type_name -> management.NetbirdConfig + 30, // 17: management.LoginResponse.peerConfig:type_name -> management.PeerConfig + 50, // 18: management.LoginResponse.Checks:type_name -> management.Checks + 62, // 19: management.ServerKeyResponse.expiresAt:type_name -> google.protobuf.Timestamp + 25, // 20: management.NetbirdConfig.stuns:type_name -> management.HostConfig + 29, // 21: management.NetbirdConfig.turns:type_name -> management.ProtectedHostConfig + 25, // 22: management.NetbirdConfig.signal:type_name -> management.HostConfig + 26, // 23: management.NetbirdConfig.relay:type_name -> management.RelayConfig + 27, // 24: management.NetbirdConfig.flow:type_name -> management.FlowConfig + 5, // 25: management.HostConfig.protocol:type_name -> management.HostConfig.Protocol + 63, // 26: management.FlowConfig.interval:type_name -> google.protobuf.Duration + 25, // 27: management.ProtectedHostConfig.hostConfig:type_name -> management.HostConfig + 36, // 28: management.PeerConfig.sshConfig:type_name -> management.SSHConfig + 31, // 29: management.PeerConfig.autoUpdate:type_name -> management.AutoUpdateSettings + 30, // 30: management.NetworkMap.peerConfig:type_name -> management.PeerConfig + 35, // 31: management.NetworkMap.remotePeers:type_name -> management.RemotePeerConfig + 42, // 32: management.NetworkMap.Routes:type_name -> management.Route + 43, // 33: management.NetworkMap.DNSConfig:type_name -> management.DNSConfig + 35, // 34: management.NetworkMap.offlinePeers:type_name -> management.RemotePeerConfig + 48, // 35: management.NetworkMap.FirewallRules:type_name -> management.FirewallRule + 52, // 36: management.NetworkMap.routesFirewallRules:type_name -> management.RouteFirewallRule + 53, // 37: management.NetworkMap.forwardingRules:type_name -> management.ForwardingRule + 33, // 38: management.NetworkMap.sshAuth:type_name -> management.SSHAuth + 60, // 39: management.SSHAuth.machine_users:type_name -> management.SSHAuth.MachineUsersEntry + 36, // 40: management.RemotePeerConfig.sshConfig:type_name -> management.SSHConfig + 28, // 41: management.SSHConfig.jwtConfig:type_name -> management.JWTConfig + 6, // 42: management.DeviceAuthorizationFlow.Provider:type_name -> management.DeviceAuthorizationFlow.provider + 41, // 43: management.DeviceAuthorizationFlow.ProviderConfig:type_name -> management.ProviderConfig + 41, // 44: management.PKCEAuthorizationFlow.ProviderConfig:type_name -> management.ProviderConfig + 46, // 45: management.DNSConfig.NameServerGroups:type_name -> management.NameServerGroup + 44, // 46: management.DNSConfig.CustomZones:type_name -> management.CustomZone + 45, // 47: management.CustomZone.Records:type_name -> management.SimpleRecord + 47, // 48: management.NameServerGroup.NameServers:type_name -> management.NameServer 2, // 49: management.FirewallRule.Direction:type_name -> management.RuleDirection 3, // 50: management.FirewallRule.Action:type_name -> management.RuleAction 1, // 51: management.FirewallRule.Protocol:type_name -> management.RuleProtocol - 50, // 52: management.FirewallRule.PortInfo:type_name -> management.PortInfo - 54, // 53: management.PortInfo.range:type_name -> management.PortInfo.Range + 51, // 52: management.FirewallRule.PortInfo:type_name -> management.PortInfo + 61, // 53: management.PortInfo.range:type_name -> management.PortInfo.Range 3, // 54: management.RouteFirewallRule.action:type_name -> management.RuleAction 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 - 50, // 58: management.ForwardingRule.destinationPort:type_name -> management.PortInfo - 50, // 59: management.ForwardingRule.translatedPort:type_name -> management.PortInfo - 33, // 60: management.SSHAuth.MachineUsersEntry.value:type_name -> management.MachineUserIndexes - 6, // 61: management.ManagementService.Login:input_type -> management.EncryptedMessage - 6, // 62: management.ManagementService.Sync:input_type -> management.EncryptedMessage - 22, // 63: management.ManagementService.GetServerKey:input_type -> management.Empty - 22, // 64: management.ManagementService.isHealthy:input_type -> management.Empty - 6, // 65: management.ManagementService.GetDeviceAuthorizationFlow:input_type -> management.EncryptedMessage - 6, // 66: management.ManagementService.GetPKCEAuthorizationFlow:input_type -> management.EncryptedMessage - 6, // 67: management.ManagementService.SyncMeta:input_type -> management.EncryptedMessage - 6, // 68: management.ManagementService.Logout:input_type -> management.EncryptedMessage - 6, // 69: management.ManagementService.Job:input_type -> management.EncryptedMessage - 6, // 70: management.ManagementService.Login:output_type -> management.EncryptedMessage - 6, // 71: management.ManagementService.Sync:output_type -> management.EncryptedMessage - 21, // 72: management.ManagementService.GetServerKey:output_type -> management.ServerKeyResponse - 22, // 73: management.ManagementService.isHealthy:output_type -> management.Empty - 6, // 74: management.ManagementService.GetDeviceAuthorizationFlow:output_type -> management.EncryptedMessage - 6, // 75: management.ManagementService.GetPKCEAuthorizationFlow:output_type -> management.EncryptedMessage - 22, // 76: management.ManagementService.SyncMeta:output_type -> management.Empty - 22, // 77: management.ManagementService.Logout:output_type -> management.Empty - 6, // 78: management.ManagementService.Job:output_type -> management.EncryptedMessage - 70, // [70:79] is the sub-list for method output_type - 61, // [61:70] is the sub-list for method input_type - 61, // [61:61] is the sub-list for extension type_name - 61, // [61:61] is the sub-list for extension extendee - 0, // [0:61] is the sub-list for field type_name + 51, // 58: management.ForwardingRule.destinationPort:type_name -> management.PortInfo + 51, // 59: management.ForwardingRule.translatedPort:type_name -> management.PortInfo + 4, // 60: management.ExposeServiceRequest.protocol:type_name -> management.ExposeProtocol + 34, // 61: management.SSHAuth.MachineUsersEntry.value:type_name -> management.MachineUserIndexes + 7, // 62: management.ManagementService.Login:input_type -> management.EncryptedMessage + 7, // 63: management.ManagementService.Sync:input_type -> management.EncryptedMessage + 23, // 64: management.ManagementService.GetServerKey:input_type -> management.Empty + 23, // 65: management.ManagementService.isHealthy:input_type -> management.Empty + 7, // 66: management.ManagementService.GetDeviceAuthorizationFlow:input_type -> management.EncryptedMessage + 7, // 67: management.ManagementService.GetPKCEAuthorizationFlow:input_type -> management.EncryptedMessage + 7, // 68: management.ManagementService.SyncMeta:input_type -> management.EncryptedMessage + 7, // 69: management.ManagementService.Logout:input_type -> management.EncryptedMessage + 7, // 70: management.ManagementService.Job:input_type -> management.EncryptedMessage + 7, // 71: management.ManagementService.CreateExpose:input_type -> management.EncryptedMessage + 7, // 72: management.ManagementService.RenewExpose:input_type -> management.EncryptedMessage + 7, // 73: management.ManagementService.StopExpose:input_type -> management.EncryptedMessage + 7, // 74: management.ManagementService.Login:output_type -> management.EncryptedMessage + 7, // 75: management.ManagementService.Sync:output_type -> management.EncryptedMessage + 22, // 76: management.ManagementService.GetServerKey:output_type -> management.ServerKeyResponse + 23, // 77: management.ManagementService.isHealthy:output_type -> management.Empty + 7, // 78: management.ManagementService.GetDeviceAuthorizationFlow:output_type -> management.EncryptedMessage + 7, // 79: management.ManagementService.GetPKCEAuthorizationFlow:output_type -> management.EncryptedMessage + 23, // 80: management.ManagementService.SyncMeta:output_type -> management.Empty + 23, // 81: management.ManagementService.Logout:output_type -> management.Empty + 7, // 82: management.ManagementService.Job:output_type -> management.EncryptedMessage + 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() } @@ -5404,7 +5849,79 @@ func file_management_proto_init() { 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{} { + 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 { case 0: return &v.state @@ -5432,8 +5949,8 @@ func file_management_proto_init() { File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_management_proto_rawDesc, - NumEnums: 6, - NumMessages: 49, + NumEnums: 7, + NumMessages: 55, NumExtensions: 0, NumServices: 1, }, diff --git a/shared/management/proto/management.proto b/shared/management/proto/management.proto index d97d66819..3667ae27f 100644 --- a/shared/management/proto/management.proto +++ b/shared/management/proto/management.proto @@ -51,6 +51,15 @@ service ManagementService { // Executes a job on a target peer (e.g., debug bundle) 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 { @@ -637,3 +646,38 @@ message ForwardingRule { // Translated port information, where the traffic should be forwarded to 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 {} diff --git a/shared/management/proto/management_grpc.pb.go b/shared/management/proto/management_grpc.pb.go index b78e21aaa..39a342041 100644 --- a/shared/management/proto/management_grpc.pb.go +++ b/shared/management/proto/management_grpc.pb.go @@ -52,6 +52,12 @@ type ManagementServiceClient interface { Logout(ctx context.Context, in *EncryptedMessage, opts ...grpc.CallOption) (*Empty, error) // Executes a job on a target peer (e.g., debug bundle) 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 { @@ -188,6 +194,33 @@ func (x *managementServiceJobClient) Recv() (*EncryptedMessage, error) { 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. // All implementations must embed UnimplementedManagementServiceServer // for forward compatibility @@ -226,6 +259,12 @@ type ManagementServiceServer interface { Logout(context.Context, *EncryptedMessage) (*Empty, error) // Executes a job on a target peer (e.g., debug bundle) 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() } @@ -260,6 +299,15 @@ func (UnimplementedManagementServiceServer) Logout(context.Context, *EncryptedMe func (UnimplementedManagementServiceServer) Job(ManagementService_JobServer) error { 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() {} // 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 } +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. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -481,6 +583,18 @@ var ManagementService_ServiceDesc = grpc.ServiceDesc{ MethodName: "Logout", 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{ { diff --git a/shared/management/proto/proxy_service.pb.go b/shared/management/proto/proxy_service.pb.go index 13fcb159e..c89157eb5 100644 --- a/shared/management/proto/proxy_service.pb.go +++ b/shared/management/proto/proxy_service.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.26.0 -// protoc v6.33.0 +// protoc v6.33.3 // source: proxy_service.proto package proto