From ac355910ff69765f4a3d77a4ecab13b65856cd87 Mon Sep 17 00:00:00 2001 From: Carl Svensson Date: Thu, 12 Dec 2024 15:45:12 +0000 Subject: [PATCH 1/2] Add Windows Service support --- cmd/pomerium-cli/tcp.go | 73 ++----------------------- cmd/pomerium-cli/tcp_impl.go | 83 ++++++++++++++++++++++++++++ cmd/pomerium-cli/tcp_windows.go | 96 +++++++++++++++++++++++++++++++++ 3 files changed, 183 insertions(+), 69 deletions(-) create mode 100644 cmd/pomerium-cli/tcp_impl.go create mode 100644 cmd/pomerium-cli/tcp_windows.go diff --git a/cmd/pomerium-cli/tcp.go b/cmd/pomerium-cli/tcp.go index 81a67bdc..47b6883e 100644 --- a/cmd/pomerium-cli/tcp.go +++ b/cmd/pomerium-cli/tcp.go @@ -1,34 +1,14 @@ +//go:build !windows + package main import ( - "context" - "crypto/tls" - "fmt" - "io" - "os" - "os/signal" - "syscall" - "github.com/spf13/cobra" - - "github.com/pomerium/cli/tunnel" ) -var tcpCmdOptions struct { - listen string - pomeriumURL string -} - func init() { - addBrowserFlags(tcpCmd) - addServiceAccountFlags(tcpCmd) - addTLSFlags(tcpCmd) flags := tcpCmd.Flags() - flags.StringVar(&tcpCmdOptions.listen, "listen", "127.0.0.1:0", - "local address to start a listener on") - flags.StringVar(&tcpCmdOptions.pomeriumURL, "pomerium-url", "", - "the URL of the pomerium server to connect to") - rootCmd.AddCommand(tcpCmd) + addTcpFlags(flags) } var tcpCmd = &cobra.Command{ @@ -36,51 +16,6 @@ var tcpCmd = &cobra.Command{ Short: "creates a TCP tunnel through Pomerium", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - destinationAddr, proxyURL, err := tunnel.ParseURLs(args[0], tcpCmdOptions.pomeriumURL) - if err != nil { - return err - } - - var tlsConfig *tls.Config - if proxyURL.Scheme == "https" { - tlsConfig, err = getTLSConfig() - if err != nil { - return err - } - } - - c := make(chan os.Signal, 1) - signal.Notify(c, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP) - ctx, cancel := context.WithCancel(context.Background()) - go func() { - <-c - cancel() - }() - - tun := tunnel.New( - tunnel.WithBrowserCommand(browserOptions.command), - tunnel.WithDestinationHost(destinationAddr), - tunnel.WithProxyHost(proxyURL.Host), - tunnel.WithServiceAccount(serviceAccountOptions.serviceAccount), - tunnel.WithServiceAccountFile(serviceAccountOptions.serviceAccountFile), - tunnel.WithTLSConfig(tlsConfig), - ) - - if tcpCmdOptions.listen == "-" { - err = tun.Run(ctx, readWriter{Reader: os.Stdin, Writer: os.Stdout}, tunnel.LogEvents()) - } else { - err = tun.RunListener(ctx, tcpCmdOptions.listen) - } - if err != nil { - _, _ = fmt.Fprintf(os.Stderr, "%s\n", err.Error()) - os.Exit(1) - } - - return nil + return runTcpForever(args[0]) }, } - -type readWriter struct { - io.Reader - io.Writer -} diff --git a/cmd/pomerium-cli/tcp_impl.go b/cmd/pomerium-cli/tcp_impl.go new file mode 100644 index 00000000..487bdf7c --- /dev/null +++ b/cmd/pomerium-cli/tcp_impl.go @@ -0,0 +1,83 @@ +package main + +import ( + "context" + "crypto/tls" + "fmt" + "io" + "os" + "os/signal" + "syscall" + + "github.com/pomerium/cli/tunnel" + "github.com/spf13/pflag" +) + +var tcpCmdOptions struct { + listen string + pomeriumURL string +} + +func addTcpFlags(flags *pflag.FlagSet) { + addBrowserFlags(tcpCmd) + addServiceAccountFlags(tcpCmd) + addTLSFlags(tcpCmd) + flags.StringVar(&tcpCmdOptions.listen, "listen", "127.0.0.1:0", + "local address to start a listener on") + flags.StringVar(&tcpCmdOptions.pomeriumURL, "pomerium-url", "", + "the URL of the pomerium server to connect to") + rootCmd.AddCommand(tcpCmd) +} + +func runTcpForever(destination string) error { + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP) + ctx, cancel := context.WithCancel(context.Background()) + go func() { + <-c + cancel() + }() + + return runTcp(ctx, destination) +} + +func runTcp(ctx context.Context, destination string) error { + destinationAddr, proxyURL, err := tunnel.ParseURLs(destination, tcpCmdOptions.pomeriumURL) + if err != nil { + return err + } + + var tlsConfig *tls.Config + if proxyURL.Scheme == "https" { + tlsConfig, err = getTLSConfig() + if err != nil { + return err + } + } + + tun := tunnel.New( + tunnel.WithBrowserCommand(browserOptions.command), + tunnel.WithDestinationHost(destinationAddr), + tunnel.WithProxyHost(proxyURL.Host), + tunnel.WithServiceAccount(serviceAccountOptions.serviceAccount), + tunnel.WithServiceAccountFile(serviceAccountOptions.serviceAccountFile), + tunnel.WithTLSConfig(tlsConfig), + ) + + if tcpCmdOptions.listen == "-" { + err = tun.Run(ctx, readWriter{Reader: os.Stdin, Writer: os.Stdout}, tunnel.LogEvents()) + } else { + err = tun.RunListener(ctx, tcpCmdOptions.listen) + } + if err != nil { + _, _ = fmt.Fprintf(os.Stderr, "%s\n", err.Error()) + os.Exit(1) + } + + return nil +} + +type readWriter struct { + io.Reader + io.Writer +} diff --git a/cmd/pomerium-cli/tcp_windows.go b/cmd/pomerium-cli/tcp_windows.go new file mode 100644 index 00000000..37f4ba79 --- /dev/null +++ b/cmd/pomerium-cli/tcp_windows.go @@ -0,0 +1,96 @@ +//go:build windows + +package main + +import ( + "context" + "fmt" + "log" + + "github.com/spf13/cobra" + + "golang.org/x/sys/windows/svc" + "golang.org/x/sys/windows/svc/debug" +) + +var tcpWindowsCmdOptions struct { + service bool +} + +func init() { + flags := tcpCmd.Flags() + addTcpFlags(flags) + flags.BoolVar(&tcpWindowsCmdOptions.service, "service", false, "emulate Windows service mode") +} + +type pomeriumCliService struct { + destination string +} + +func (m *pomeriumCliService) Execute(args []string, r <-chan svc.ChangeRequest, status chan<- svc.Status) (bool, uint32) { + + const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown + + status <- svc.Status{State: svc.StartPending} + + ctx, cancel := context.WithCancel(context.Background()) + + go runTcp(ctx, m.destination) + + status <- svc.Status{State: svc.Running, Accepts: cmdsAccepted} + +loop: + for { + select { + case c := <-r: + switch c.Cmd { + case svc.Interrogate: + status <- c.CurrentStatus + case svc.Stop, svc.Shutdown: + status <- svc.Status{State: svc.StopPending} + log.Print("Shutting down Pomerium tunnel service...") + cancel() + break loop + default: + log.Printf("Unexpected service control request #%d", c) + } + } + } + + status <- svc.Status{State: svc.Stopped} + return false, 0 +} + +func runService(name string, destination string, isDebug bool) error { + if isDebug { + err := debug.Run(name, &pomeriumCliService{destination}) + if err != nil { + return fmt.Errorf("Error running service in debug mode: %w", err) + } + } else { + err := svc.Run(name, &pomeriumCliService{destination}) + if err != nil { + return fmt.Errorf("Error running service in Service Control mode: %w", err) + } + } + return nil +} + +var tcpCmd = &cobra.Command{ + Use: "tcp destination", + Short: "creates a TCP tunnel through Pomerium", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + + isService, err := svc.IsWindowsService() + if err != nil { + return fmt.Errorf("Could not determine service status: %w", err) + } + + if isService || *&tcpWindowsCmdOptions.service { + return runService("Pomerium CLI", args[0], !isService) + } else { + return runTcpForever(args[0]) + } + }, +} From 92c974f2a9a9a060db2d950e49658c3da6f758eb Mon Sep 17 00:00:00 2001 From: Carl Svensson Date: Fri, 15 Aug 2025 18:00:18 +0100 Subject: [PATCH 2/2] Fix merge bug --- cmd/pomerium-cli/tcp_impl.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/pomerium-cli/tcp_impl.go b/cmd/pomerium-cli/tcp_impl.go index 2c6f2f11..3d2f7ca9 100644 --- a/cmd/pomerium-cli/tcp_impl.go +++ b/cmd/pomerium-cli/tcp_impl.go @@ -42,7 +42,7 @@ func runTcpForever(destination string) error { } func runTcp(ctx context.Context, destination string) error { - destinationAddr, proxyURL, err := tunnel.ParseURLs(args[0], tcpCmdOptions.pomeriumURL) + destinationAddr, proxyURL, err := tunnel.ParseURLs(destination, tcpCmdOptions.pomeriumURL) if err != nil { return err }