Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Automatically enable tunnel for "tsh proxy db" #47716

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
23 changes: 23 additions & 0 deletions tool/tsh/common/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -1783,6 +1783,18 @@ func formatAmbiguousDB(cf *CLIConf, selectors resourceSelectors, matchedDBs type
return formatAmbiguityErrTemplate(cf, selectors, listCommand, sb.String(), fullNameExample)
}

// formatDbProxyAutoTunnel makes the message printed when "tsh proxy db"
// automatically enables the "--tunnel" flag.
func formatDbProxyAutoTunnel(reasons ...string) string {
templateData := map[string]any{
"reasons": reasons,
}

buf := bytes.NewBuffer(nil)
_ = dbProxyAutoTunnelTemplate.Execute(buf, templateData)
return buf.String()
}

// resourceSelectors is a helper struct for gathering up the selectors for a
// resource, as an aggregate of name, labels, and predicate query.
type resourceSelectors struct {
Expand Down Expand Up @@ -1870,6 +1882,17 @@ Please use one of the following commands to connect to the database:
{{.}}{{end -}}
{{- end}}`))

// dbProxyAutoTunnelTemplate is the message printed when "tsh proxy db"
// automatically enables the "--tunnel" flag.
dbProxyAutoTunnelTemplate = template.Must(template.New("").Parse(`Note: "--tunnel" flag has been automatically enabled{{if .reasons}} when:
{{- range $reason := .reasons }}
- {{ $reason }}.
{{- end}}
{{- else}}.
{{- end}}
To avoid this note, please add the "--tunnel" flag to this "tsh proxy db" command.

`))
// dbConnectTemplate is the message printed after a successful "tsh db login" on how to connect.
dbConnectTemplate = template.Must(template.New("").Parse(`Connection information for database "{{ .name }}" has been saved.

Expand Down
53 changes: 53 additions & 0 deletions tool/tsh/common/db_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1972,3 +1972,56 @@ func Test_shouldRetryGetDatabaseUsingSearchAsRoles(t *testing.T) {
})
}
}

func Test_maybeEnableDbProxyTunnel(t *testing.T) {
tests := []struct {
name string
dbRequiresTunnel bool
tunnelFlagProvided bool
wantTunnelFlagEnabled bool
wantMessage string
}{
{
name: "tunnel already enabled",
dbRequiresTunnel: true,
tunnelFlagProvided: true,
wantTunnelFlagEnabled: true,
},
{
name: "tunnel not required",
dbRequiresTunnel: false,
tunnelFlagProvided: false,
wantTunnelFlagEnabled: false,
},
{
name: "tunnel enabled",
dbRequiresTunnel: true,
tunnelFlagProvided: false,
wantTunnelFlagEnabled: true,
wantMessage: `Note: "--tunnel" flag has been automatically enabled when:
- tunnel required by unit test.
To avoid this note, please add the "--tunnel" flag to this "tsh proxy db" command.

`,
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
var buf bytes.Buffer
cf := &CLIConf{
LocalProxyTunnel: test.tunnelFlagProvided,
OverrideStdout: &buf,
}
requires := &dbLocalProxyRequirement{
tunnel: test.dbRequiresTunnel,
tunnelReasons: []string{"tunnel required by unit test"},
}

maybeEnableDbProxyTunnel(cf, requires)

require.Equal(t, test.wantTunnelFlagEnabled, cf.LocalProxyTunnel)
require.Equal(t, test.wantMessage, buf.String())
})
}
}
20 changes: 14 additions & 6 deletions tool/tsh/common/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,12 +172,7 @@ func onProxyCommandDB(cf *CLIConf) error {
// These steps are not needed with `--tunnel`, because the local proxy tunnel
// will manage database certificates itself and reissue them as needed.
requires := getDBLocalProxyRequirement(tc, dbInfo.RouteToDatabase)
if requires.tunnel && !cf.LocalProxyTunnel {
// Some scenarios require a local proxy tunnel, e.g.:
// - Snowflake, DynamoDB protocol
// - Hardware-backed private key policy
return trace.BadParameter(formatDbCmdUnsupported(cf, dbInfo.RouteToDatabase, requires.tunnelReasons...))
}
maybeEnableDbProxyTunnel(cf, requires)
if err := maybeDatabaseLogin(cf, tc, profile, dbInfo, requires); err != nil {
return trace.Wrap(err)
}
Expand Down Expand Up @@ -291,6 +286,19 @@ func onProxyCommandDB(cf *CLIConf) error {
return nil
}

// maybeEnableDbProxyTunnel forces cf.LocalProxyTunnel to true for scenarios require
// a local proxy tunnel, e.g.:
// - Snowflake, DynamoDB protocol
// - Hardware-backed private key policy
func maybeEnableDbProxyTunnel(cf *CLIConf, requires *dbLocalProxyRequirement) {
if requires.tunnel && !cf.LocalProxyTunnel {
cf.LocalProxyTunnel = true

msg := formatDbProxyAutoTunnel(requires.tunnelReasons...)
fmt.Fprintf(cf.Stdout(), msg)
}
}

func maybeAddDBUserPassword(cf *CLIConf, tc *libclient.TeleportClient, dbInfo *databaseInfo, opts []dbcmd.ConnectCommandFunc) ([]dbcmd.ConnectCommandFunc, error) {
if dbInfo.Protocol == defaults.ProtocolCassandra {
db, err := dbInfo.GetDatabase(cf.Context, tc)
Expand Down
Loading