Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
799c039
add draft of implementation
mlsmaycon Feb 15, 2026
57a6d2a
fix mocks
mlsmaycon Feb 15, 2026
96023fc
Merge branch 'main' into feature/client-service-expose
mlsmaycon Feb 19, 2026
fbcb81e
replace stream protocol
mlsmaycon Feb 19, 2026
96086d0
update reverseproxy service with source type changes and last renewed…
mlsmaycon Feb 19, 2026
b50909b
add permission validation, expiration handling, and peer context to r…
mlsmaycon Feb 20, 2026
c9515c7
add auth metadata
mlsmaycon Feb 21, 2026
9ed8bb5
rename commands
mlsmaycon Feb 21, 2026
47119ec
enhance reverseproxy service with prefix validation, mutex for concur…
mlsmaycon Feb 21, 2026
692ee3f
add additional tests for reverseproxy and grpc modules, improve error…
mlsmaycon Feb 21, 2026
72b201e
fix mock behavior
mlsmaycon Feb 21, 2026
829fc8a
fix comments
mlsmaycon Feb 21, 2026
e5571aa
fix last seem timestamp
mlsmaycon Feb 21, 2026
0d98f22
fix mock
mlsmaycon Feb 21, 2026
492a716
use pointer type for ServiceMeta.CertificateIssuedAt to avoid MySQL z…
mlsmaycon Feb 21, 2026
40eaf6d
enhance reverseproxy module with dynamic protocol mapping, improve er…
mlsmaycon Feb 21, 2026
d30e25c
refactor expose service flow: introduce manager to simplify lifecycle…
mlsmaycon Feb 21, 2026
1175f1e
add pin validation to expose service and command, introduce comprehen…
mlsmaycon Feb 21, 2026
01725f9
improve expose command flag descriptions for clarity and add usage me…
mlsmaycon Feb 21, 2026
3c568d7
add validation for peer expose group settings
mlsmaycon Feb 21, 2026
1714c35
add support for tracking account peer expose settings changes
mlsmaycon Feb 21, 2026
fb61d2d
refactor expose command: extract flag validation into a dedicated fun…
mlsmaycon Feb 21, 2026
38feca6
refactor context handling in expose logic and remove redundant peer r…
mlsmaycon Feb 21, 2026
9621c10
update expose command description
mlsmaycon Feb 22, 2026
96e6dc2
introduce mgm client types
mlsmaycon Feb 23, 2026
e6ebc87
lock before return GetExposeManager
mlsmaycon Feb 23, 2026
dd93d98
use expose types and use int for pin and port
mlsmaycon Feb 23, 2026
12d6626
use string for pin and adjust code with new types
mlsmaycon Feb 23, 2026
c3ad534
move keepalive to expose manager
mlsmaycon Feb 23, 2026
0494f39
allow https exposure
mlsmaycon Feb 23, 2026
59f4fab
use account settings instead
mlsmaycon Feb 23, 2026
4206574
remove unused messages from proto
mlsmaycon Feb 23, 2026
b1488f4
Merge branch 'main' into feature/client-service-expose
mlsmaycon Feb 23, 2026
3d99476
refactor reverseproxy manager to use sendServiceUpdate function
mlsmaycon Feb 23, 2026
b0b4d52
use settings mock manager in reverseproxy manager tests
mlsmaycon Feb 23, 2026
c5c46bf
use status errors instead of fmt errors in reverseproxy manager
mlsmaycon Feb 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 143 additions & 0 deletions client/cmd/expose.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
package cmd

import (
"context"
"fmt"
"os"
"os/signal"
"strconv"
"syscall"

log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"

"github.com/netbirdio/netbird/client/proto"
"github.com/netbirdio/netbird/util"
)

var (
exposePin string
exposePassword string
exposeUserGroups []string
exposeDomain string
exposeNamePrefix string
exposeProtocol string
)

var exposeCmd = &cobra.Command{
Use: "expose <port>",
Short: "Expose a local port via the NetBird reverse proxy",
Args: cobra.ExactArgs(1),
RunE: exposeFn,
}

func init() {
exposeCmd.Flags().StringVar(&exposePin, "with-pin", "", "Protect the exposed service with a PIN")
exposeCmd.Flags().StringVar(&exposePassword, "with-password", "", "Protect the exposed service with a password")
exposeCmd.Flags().StringSliceVar(&exposeUserGroups, "with-user-groups", nil, "Restrict access to specific user groups")
exposeCmd.Flags().StringVar(&exposeDomain, "with-custom-domain", "", "Custom domain for the exposed service. Must be configured to your account")
exposeCmd.Flags().StringVar(&exposeNamePrefix, "with-name-prefix", "", "Prefix for the generated service name")
exposeCmd.Flags().StringVar(&exposeProtocol, "protocol", "http", "Protocol to use (only 'http' is supported)")
}

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
}

port, err := strconv.ParseUint(args[0], 10, 32)
if err != nil {
return fmt.Errorf("invalid port number: %s", args[0])
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
if port == 0 || port > 65535 {
return fmt.Errorf("invalid port number: must be between 1 and 65535")
}

if exposeProtocol != "http" {
return fmt.Errorf("unsupported protocol %q: only 'http' is supported", exposeProtocol)
}

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)

req := &proto.ExposeServiceRequest{
Port: uint32(port),
Protocol: proto.ExposeProtocol_EXPOSE_HTTP,
Comment thread
mlsmaycon marked this conversation as resolved.
Outdated
Pin: exposePin,
Password: exposePassword,
UserGroups: exposeUserGroups,
Domain: exposeDomain,
NamePrefix: exposeNamePrefix,
}

stream, err := client.ExposeService(ctx, req)
if err != nil {
return fmt.Errorf("expose service: %w", err)
}

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.")
case *proto.ExposeServiceEvent_Error:
return fmt.Errorf("expose failed: %s", e.Error.Message)
case *proto.ExposeServiceEvent_Stopped:
return fmt.Errorf("expose stopped: %s", e.Stopped.Reason)
default:
return fmt.Errorf("unexpected expose event: %T", event.Event)
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

for {
event, err := stream.Recv()
if err != nil {
if ctx.Err() != nil {
cmd.Println("\nService stopped.")
return nil
}
return err
}

switch e := event.Event.(type) {
case *proto.ExposeServiceEvent_Stopped:
cmd.Printf("\nService stopped: %s\n", e.Stopped.Reason)
return nil
case *proto.ExposeServiceEvent_Error:
return fmt.Errorf("expose error: %s", e.Error.Message)
default:
log.Debugf("unexpected expose event: %T", event.Event)
}
}
}
1 change: 1 addition & 0 deletions client/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Loading
Loading