feat(clientproxy): automatically close boundary connect when proxy is no longer in use (#6232)
Co-authored-by: Andrew Gaffney <andrew@gaffney.cc>
This commit is contained in:
1 parent
d0050ad881
commit
08015e9d98
7 files changed
+289
-48
No files matched your search
@@ -69,6 +69,7 @@ type Command struct {
|
||||
flagUsername string
|
||||
flagDbname string
|
||||
flagMongoDbAuthenticationDatabase string
|
||||
flagInactiveTimeout time.Duration
|
||||
|
||||
// HTTP
|
||||
httpFlags
|
||||
@@ -220,6 +221,13 @@ func (c *Command) Flags() *base.FlagSets {
|
||||
Usage: "Target scope name, if authorizing the session via scope parameters and target name. Mutually exclusive with -scope-id.",
|
||||
})
|
||||
|
||||
f.DurationVar(&base.DurationVar{
|
||||
Name: "inactive-timeout",
|
||||
Target: &c.flagInactiveTimeout,
|
||||
Completion: complete.PredictAnything,
|
||||
Usage: "How long to wait between connections before closing the session. Increase this value if the proxy closes during long-running processes, or use -1 to disable the timeout.",
|
||||
})
|
||||
|
||||
switch c.Func {
|
||||
case "connect":
|
||||
f.StringVar(&base.StringVar{
|
||||
@@ -508,11 +516,32 @@ func (c *Command) Run(args []string) (retCode int) {
|
||||
clientProxyCloseCh := make(chan struct{})
|
||||
connCountCloseCh := make(chan struct{})
|
||||
|
||||
if c.flagInactiveTimeout == 0 {
|
||||
// no timeout was specified by the user, so use our defaults based on subcommand
|
||||
switch c.Func {
|
||||
case "connect":
|
||||
// connect is when there is no subcommand specified, this case should
|
||||
// have the most generous timeout
|
||||
apiProxyOpts = append(apiProxyOpts, apiproxy.WithInactivityTimeout(30*time.Second))
|
||||
case "rdp":
|
||||
// rdp has a gui, so give the user a chance to click "reconnect"
|
||||
apiProxyOpts = append(apiProxyOpts, apiproxy.WithInactivityTimeout(5*time.Second))
|
||||
case "ssh":
|
||||
// one second is probably enough for ssh
|
||||
apiProxyOpts = append(apiProxyOpts, apiproxy.WithInactivityTimeout(time.Second))
|
||||
default:
|
||||
// for other protocols, give some extra leeway just in case
|
||||
apiProxyOpts = append(apiProxyOpts, apiproxy.WithInactivityTimeout(3*time.Second))
|
||||
}
|
||||
} else {
|
||||
apiProxyOpts = append(apiProxyOpts, apiproxy.WithInactivityTimeout(c.flagInactiveTimeout))
|
||||
}
|
||||
|
||||
proxyError := new(atomic.Error)
|
||||
go func() {
|
||||
defer close(clientProxyCloseCh)
|
||||
if err = clientProxy.Start(); err != nil {
|
||||
c.proxyCancel()
|
||||
defer c.proxyCancel()
|
||||
if err = clientProxy.Start(apiProxyOpts...); err != nil {
|
||||
proxyError.Store(err)
|
||||
}
|
||||
}()
|
||||
@@ -595,10 +624,8 @@ func (c *Command) Run(args []string) (retCode int) {
|
||||
if c.execCmdReturnValue != nil {
|
||||
// Don't print out in this case, so ensure we clear it
|
||||
termInfo.Reason = ""
|
||||
} else if time.Now().After(clientProxy.SessionExpiration()) {
|
||||
termInfo.Reason = "Session has expired"
|
||||
} else if clientProxy.ConnectionsLeft() == 0 {
|
||||
termInfo.Reason = "No connections left in session"
|
||||
} else if r := clientProxy.CloseReason(); r != "" {
|
||||
termInfo.Reason = r
|
||||
} else if err := proxyError.Load(); err != nil {
|
||||
termInfo.Reason = "Error from proxy client: " + err.Error()
|
||||
}
|
||||
@@ -825,10 +852,9 @@ func (c *Command) handleExec(clientProxy *apiproxy.ClientProxy, passthroughArgs
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
cmdExit := make(chan struct{})
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
exitCode := 2
|
||||
|
||||
cmdError := func(err error) {
|
||||
if exitError, ok := err.(*exec.ExitError); ok {
|
||||
if exitError.Success() {
|
||||
c.execCmdReturnValue.Store(0)
|
||||
@@ -841,8 +867,30 @@ func (c *Command) handleExec(clientProxy *apiproxy.ClientProxy, passthroughArgs
|
||||
}
|
||||
|
||||
c.PrintCliError(fmt.Errorf("Failed to run command: %w", err))
|
||||
c.execCmdReturnValue.Store(int32(exitCode))
|
||||
c.execCmdReturnValue.Store(2)
|
||||
return
|
||||
}
|
||||
c.execCmdReturnValue.Store(0)
|
||||
|
||||
go func() {
|
||||
defer close(cmdExit)
|
||||
if err := cmd.Start(); err != nil {
|
||||
cmdError(err)
|
||||
return
|
||||
}
|
||||
if err := cmd.Wait(); err != nil {
|
||||
cmdError(err)
|
||||
return
|
||||
}
|
||||
c.execCmdReturnValue.Store(0)
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-c.proxyCtx.Done():
|
||||
// the proxy exited for some reason, end the cmd since connections are no longer possible
|
||||
_ = endProcess(cmd.Process)
|
||||
case <-cmdExit:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
//go:build !windows
|
||||
|
||||
package connect
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// endProcess gracefully ends the provided os process
|
||||
func endProcess(p *os.Process) error {
|
||||
if p == nil {
|
||||
return nil
|
||||
}
|
||||
return p.Signal(syscall.SIGTERM)
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
//go:build windows
|
||||
|
||||
package connect
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
// endProcess kills the provided os process
|
||||
func endProcess(p *os.Process) error {
|
||||
if p == nil {
|
||||
return nil
|
||||
}
|
||||
return p.Kill()
|
||||
}
|
||||
@@ -59,7 +59,7 @@ func (r *rdpFlags) buildArgs(c *Command, port, ip, addr string) []string {
|
||||
case "mstsc.exe":
|
||||
args = append(args, "/v", addr)
|
||||
case "open":
|
||||
args = append(args, "-n", "-W", fmt.Sprintf("rdp://full%saddress=s%s%s", "%20", "%3A", url.QueryEscape(addr)))
|
||||
args = append(args, "-W", fmt.Sprintf("rdp://full%saddress=s%s%s", "%20", "%3A", url.QueryEscape(addr)))
|
||||
}
|
||||
return args
|
||||
}
|
||||
Reference in new issue
Block a user