From a7442b149880f4efe848a81adedbc6c60d38aa49 Mon Sep 17 00:00:00 2001 From: Caleb Doxsey Date: Tue, 21 Sep 2021 08:31:30 -0600 Subject: [PATCH] pomerium-cli: add support for a custom browser command (#2617) --- cmd/pomerium-cli/kubernetes.go | 5 ++++- cmd/pomerium-cli/main.go | 10 ++++++++++ cmd/pomerium-cli/tcp.go | 1 + internal/authclient/authclient.go | 12 ++++++++---- internal/authclient/authclient_test.go | 8 ++------ internal/authclient/config.go | 17 +++++++++++++++++ internal/tcptunnel/config.go | 16 ++++++++++++---- internal/tcptunnel/tcptunnel.go | 6 ++++-- 8 files changed, 58 insertions(+), 17 deletions(-) diff --git a/cmd/pomerium-cli/kubernetes.go b/cmd/pomerium-cli/kubernetes.go index 3bd657276..05e727853 100644 --- a/cmd/pomerium-cli/kubernetes.go +++ b/cmd/pomerium-cli/kubernetes.go @@ -16,6 +16,7 @@ import ( ) func init() { + addBrowserFlags(kubernetesExecCredentialCmd) addTLSFlags(kubernetesExecCredentialCmd) kubernetesCmd.AddCommand(kubernetesExecCredentialCmd) kubernetesCmd.AddCommand(kubernetesFlushCredentialsCmd) @@ -61,7 +62,9 @@ var kubernetesExecCredentialCmd = &cobra.Command{ tlsConfig = getTLSConfig() } - ac := authclient.New(authclient.WithTLSConfig(tlsConfig)) + ac := authclient.New( + authclient.WithBrowserCommand(browserOptions.command), + authclient.WithTLSConfig(tlsConfig)) rawJWT, err := ac.GetJWT(context.Background(), serverURL) if err != nil { fatalf("%s", err) diff --git a/cmd/pomerium-cli/main.go b/cmd/pomerium-cli/main.go index 893179b1b..8a8c15fec 100644 --- a/cmd/pomerium-cli/main.go +++ b/cmd/pomerium-cli/main.go @@ -57,3 +57,13 @@ func getTLSConfig() *tls.Config { } return cfg } + +var browserOptions struct { + command string +} + +func addBrowserFlags(cmd *cobra.Command) { + flags := cmd.Flags() + flags.StringVar(&browserOptions.command, "browser-cmd", "", + "custom browser command to run when opening a URL") +} diff --git a/cmd/pomerium-cli/tcp.go b/cmd/pomerium-cli/tcp.go index 24612b480..9a55e6960 100644 --- a/cmd/pomerium-cli/tcp.go +++ b/cmd/pomerium-cli/tcp.go @@ -87,6 +87,7 @@ var tcpCmd = &cobra.Command{ }() tun := tcptunnel.New( + tcptunnel.WithBrowserCommand(browserOptions.command), tcptunnel.WithDestinationHost(dstHost), tcptunnel.WithProxyHost(pomeriumURL.Host), tcptunnel.WithTLSConfig(tlsConfig), diff --git a/internal/authclient/authclient.go b/internal/authclient/authclient.go index 4c49842b5..e58051b5c 100644 --- a/internal/authclient/authclient.go +++ b/internal/authclient/authclient.go @@ -9,14 +9,12 @@ import ( "net" "net/http" "net/url" + "os" "time" - "github.com/skratchdot/open-golang/open" "golang.org/x/sync/errgroup" ) -var openBrowser = open.Run - // An AuthClient retrieves an authentication JWT via the Pomerium login API. type AuthClient struct { cfg *config @@ -141,5 +139,11 @@ func (client *AuthClient) runOpenBrowser(ctx context.Context, li net.Listener, s return fmt.Errorf("failed to read login url: %w", err) } - return openBrowser(string(bs)) + err = client.cfg.open(string(bs)) + if err != nil { + return fmt.Errorf("failed to open browser url: %w", err) + } + + _, _ = fmt.Fprintf(os.Stderr, "Your browser has been opened to visit:\n\n%s\n\n", string(bs)) + return nil } diff --git a/internal/authclient/authclient_test.go b/internal/authclient/authclient_test.go index e3061aa40..b4f4384d1 100644 --- a/internal/authclient/authclient_test.go +++ b/internal/authclient/authclient_test.go @@ -36,11 +36,8 @@ func TestAuthClient(t *testing.T) { _ = srv.Serve(li) }() - origOpenBrowser := openBrowser - defer func() { - openBrowser = origOpenBrowser - }() - openBrowser = func(input string) error { + ac := New() + ac.cfg.open = func(input string) error { u, err := url.Parse(input) if err != nil { return err @@ -64,7 +61,6 @@ func TestAuthClient(t *testing.T) { return nil } - ac := New() rawJWT, err := ac.GetJWT(ctx, &url.URL{ Scheme: "http", Host: li.Addr().String(), diff --git a/internal/authclient/config.go b/internal/authclient/config.go index b272e8523..357a0f82d 100644 --- a/internal/authclient/config.go +++ b/internal/authclient/config.go @@ -2,14 +2,18 @@ package authclient import ( "crypto/tls" + + "github.com/skratchdot/open-golang/open" ) type config struct { + open func(rawURL string) error tlsConfig *tls.Config } func getConfig(options ...Option) *config { cfg := new(config) + WithBrowserCommand("")(cfg) for _, o := range options { o(cfg) } @@ -19,6 +23,19 @@ func getConfig(options ...Option) *config { // An Option modifies the config. type Option func(*config) +// WithBrowserCommand returns an option to configure the browser command. +func WithBrowserCommand(browserCommand string) Option { + return func(cfg *config) { + if browserCommand == "" { + cfg.open = open.Run + } else { + cfg.open = func(rawURL string) error { + return open.RunWith(rawURL, browserCommand) + } + } + } +} + // WithTLSConfig returns an option to configure the tls config. func WithTLSConfig(tlsConfig *tls.Config) Option { return func(cfg *config) { diff --git a/internal/tcptunnel/config.go b/internal/tcptunnel/config.go index aead3e3df..72c69c1a1 100644 --- a/internal/tcptunnel/config.go +++ b/internal/tcptunnel/config.go @@ -9,10 +9,11 @@ import ( ) type config struct { - jwtCache cliutil.JWTCache - dstHost string - proxyHost string - tlsConfig *tls.Config + jwtCache cliutil.JWTCache + dstHost string + proxyHost string + tlsConfig *tls.Config + browserConfig string } func getConfig(options ...Option) *config { @@ -32,6 +33,13 @@ func getConfig(options ...Option) *config { // An Option modifies the config. type Option func(*config) +// WithBrowserCommand returns an option to configure the browser command. +func WithBrowserCommand(browserCommand string) Option { + return func(cfg *config) { + cfg.browserConfig = browserCommand + } +} + // WithDestinationHost returns an option to configure the destination host. func WithDestinationHost(dstHost string) Option { return func(cfg *config) { diff --git a/internal/tcptunnel/tcptunnel.go b/internal/tcptunnel/tcptunnel.go index 48bde298f..f98a118b9 100644 --- a/internal/tcptunnel/tcptunnel.go +++ b/internal/tcptunnel/tcptunnel.go @@ -30,8 +30,10 @@ type Tunnel struct { func New(options ...Option) *Tunnel { cfg := getConfig(options...) return &Tunnel{ - cfg: cfg, - auth: authclient.New(authclient.WithTLSConfig(cfg.tlsConfig)), + cfg: cfg, + auth: authclient.New( + authclient.WithBrowserCommand(cfg.browserConfig), + authclient.WithTLSConfig(cfg.tlsConfig)), } }