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

Add tbot proxy and tbot db wrapper commands #12687

Merged
merged 29 commits into from
May 27, 2022

Conversation

timothyb89
Copy link
Contributor

@timothyb89 timothyb89 commented May 17, 2022

This adds new wrapper commands that leverage tsh for proxy and database access.

It also adds a new tshwrap helper package which contains utilities for locating the tsh executable, checking its version, and loading all necessary data (certificates, destinations, etc) that will need to be passed to tsh for wrapped commands to function.

Usage examples:

  • SSH: ssh -F tbot-user/ssh_config user@host.example.com
    (should now work both with and without the legacy SSH proxy port enabled)
  • Database connect: tbot db --destination-dir=./tbot-user --proxy=proxy.example.com connect db-name
  • Database proxy: tbot proxy --destination-dir=./tbot-user --proxy=proxy.example.com:3080 db db-name
  • Database proxy with authenticated tunnel: tbot proxy --destination-dir=./tbot-user --proxy=proxy.example.com:3080 --tunnel db-name

Fixes #11445

This enhances support for identity files in tsh, which previously only
worked for regular SSH access. The largest blocker for support is that
tsh uses profiles for all non-SSH resource access, and profiles have a
direct mapping to some on-disk resources. This patch works around this
in a few ways:
 * Virtual profiles: When an identity file is specified with `-i`, we
   use it to create an in-memory virtual profile using the cert as the
   root identity _and_ for every `RouteToDatabase` (and in the future,
   app) field contained in the cert.
 * Virtual profile paths: Certain profile operations require paths to
   valid certificates and other resources on disk, which may not exist
   inside the identity file.

   As the driving use-case for this change is integration with Machine
   ID, we can "cheat" and pass the correct paths to tsh via
   environment variables. A cooperating wrapper in `tbot` will execute
   `tsh` with appropriate flags and environment variables, which
   override tsh's usual certifiate paths. This allows commands like
   `tsh db connect ...` to work as expected.
 * Key stores: previously we used a `noLocalKeyStore{}` with which all
   lookups fail. This patch replaces it with an in-memory keystore if
   a client key is available.
 * Profile status: lastly, we add a new `StatusCurrentWithIdentity()`
   function to load virtual profiles where supported. Some commands
   are not supported in this PR (like `tsh app ...`), but others
   don't make sense to support (like cert reissuing).

   We might consider merging everything into the traditional
   `StatusCurrent()` when adding app support.

App access is still broken and will be addressed in a later change.

Partially fixes #11770
@timothyb89
Copy link
Contributor Author

timothyb89 commented May 17, 2022

Outstanding TODOs:

This adds new wrapper commands that leverage tsh for proxy and
database access.

It also adds a new `tshwrap` helper package which contains utilities
for locating the tsh executable, checking its version, and loading
all necessary data (certificates, destinations, etc) that will need
to be passed to tsh for wrapped commands to function.
@timothyb89 timothyb89 force-pushed the timothyb89/tbot-proxy-wrappers branch from 325def1 to be0153b Compare May 17, 2022 01:19
Base automatically changed from timothyb89/tsh-expanded-identityfiles to master May 24, 2022 21:58
@timothyb89 timothyb89 marked this pull request as ready for review May 25, 2022 00:22
Copy link
Contributor

@strideynet strideynet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks solid. There's a test failure for TestDefaultTemplateRendering which may be relevant though.

It might be easier to rebase this on top of #12855 when thats been merged rather than vice versa.

// sshConfigUnsupportedWarning is used to ensure we don't spam log messages if
// using non-filesystem backends.
var sshConfigUnsupportedWarning sync.Once

func (c *TemplateSSHClient) Render(ctx context.Context, authClient auth.ClientI, currentIdentity *identity.Identity, destination *DestinationConfig) error {
Copy link
Contributor

@strideynet strideynet May 25, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be super nice to include a unit-test for this function using the golden helper, so we can easily see what changes to the logic result in what changes to the generated file in PRs though. More than willing to provide some advice on this, or I will do it post this PR being merged.

Copy link
Contributor

@strideynet strideynet May 25, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've gone ahead and done this in #12898 . It should be simple enough to pull that commit into this branch, or approve and merge that PR and then rebase onto it.

You'll need to run the following to regenerate the golden file for your changes in this PR:

GOLDEN_UPDATE=1 go test -run ^TestTemplateSSHClient_Render$ github.com/gravitational/teleport/tool/tbot/config

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am a big fan of making the result of these changes obvious during review. Any objections if we try to get this in after v9.3?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, yep, I've fixed the failing test, making ssh_config optional broke it.

I do like the golden approach but let's aim to include that post-v9.3.

tool/tbot/tshwrap/wrap.go Show resolved Hide resolved
tool/tbot/tshwrap/wrap.go Show resolved Hide resolved
tool/tbot/tshwrap/wrap.go Show resolved Hide resolved
@timothyb89
Copy link
Contributor Author

It might be easier to rebase this on top of #12855 when thats been merged rather than vice versa.

A caveat here is that we are looking to squeeze this into v9.3 (end of this week). I have mixed feelings about also squeezing in and backporting a refactoring patch, especially with the timeline constraints. What do you think?

tool/tbot/main.go Outdated Show resolved Hide resolved
tool/tbot/proxy.go Outdated Show resolved Hide resolved
@strideynet
Copy link
Contributor

It might be easier to rebase this on top of #12855 when thats been merged rather than vice versa.

A caveat here is that we are looking to squeeze this into v9.3 (end of this week). I have mixed feelings about also squeezing in and backporting a refactoring patch, especially with the timeline constraints. What do you think?

If that's the case, just get that into 9.3 :)

This fixes a failing unit test by making the description for
`ssh_config` match its behavior in practice. This necessitated
passing the destination to all templates, unfortunately.
Copy link
Contributor

@codingllama codingllama left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apologies for the delay.

tool/tbot/config/configtemplate_ssh_client.go Outdated Show resolved Hide resolved
tool/tbot/config/configtemplate_ssh_client.go Show resolved Hide resolved
tool/tbot/config/configtemplate_ssh_client.go Outdated Show resolved Hide resolved
tool/tbot/config/configtemplate_ssh_client.go Show resolved Hide resolved
@@ -88,7 +88,7 @@ type Template interface {
// statically as this must be callable without any auth clients (or any
// secrets) for use with `tbot init`. If an arbitrary number of files must
// be generated, they should be placed in a subdirectory.
Describe() []FileDescription
Describe(destination destination.Destination) []FileDescription
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note for some other time: "destination.Destination" stutters, "destination destination.Destination" triply so. When you get some spare cycles (if ever), consider renaming package and/or type to a combination that doesn't stutters and takes better advantage of both package and type name.

https://github.com/golang/go/wiki/CodeReviewComments#package-names

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's come up before and I agree that it's ugly, but this practically needs to live in its own package to avoid import cycles. Since it's the only thing that will ever live in that package, there's few names that would make sense other than, well, destination. Always open to suggestions if you've got a more creative solution to offer 🙂

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having a quick look the only implementor I found was DestinationDirectory. The destination package holds an interface and nothing else, which is a bit of a design smell for Go.

I would suggest the following:

  • Rename package destination to package destinations. The main reason is that we can have variables named destination without overshadowing the package name (similar to strings or bytes).
  • Move DestinationDirectory to destinations.Directory. Names fit nicely and the package now exports something concrete. (IIUC, this won't cause import cycles.)
  • Ditch the interface, or at the very least ditch it from the destinations package. If you do need an interface, spring up one where it's used, preferably with only 1 or 2 methods that the code cares about.

We don't usually need "preemptive" interfaces in Go. Code against the concrete type if you can; if you need an interface, spit it out where it's needed, and only after it has a reason to be there. (The api/ package is the one place where we may want to do differently, but for internal code this should work just fine.)

tool/tbot/tshwrap/wrap_test.go Outdated Show resolved Hide resolved
tool/tbot/tshwrap/wrap_test.go Outdated Show resolved Hide resolved
tool/tbot/tshwrap/wrap_test.go Outdated Show resolved Hide resolved
tool/tbot/tshwrap/wrap_test.go Show resolved Hide resolved
tool/tbot/tshwrap/wrap_test.go Outdated Show resolved Hide resolved
Copy link
Contributor

@codingllama codingllama left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a few ramblings / minor suggestions. Thanks for the fixes.

// Remaining args are stored directly to a []string rather than written to
// a shared ref like most other kingpin args, so we'll need to manually
// move them to the remaining args field.
if len(*dbRemaining) > 0 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kingpin is a bit of a strange choice, cobra is much more common nowadays (I think it leads to better code too, no need for that "what's the command" switch). I assume Teleport predates cobra, hence kingpin.

Just idle musings, no actions expected here.

tool/tbot/db.go Outdated Show resolved Hide resolved
tool/tbot/tshwrap/wrap.go Outdated Show resolved Hide resolved
const (
// TSHVarName is the name of the environment variable that can override the
// tsh path that would otherwise be located on the $PATH.
TSHVarName = "TSH"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's fair. Maybe tshwrap.EnvVarName would cut it, but it's fine as-is.

tool/tbot/config/configtemplate_cockroach.go Show resolved Hide resolved
tool/tbot/tshwrap/wrap.go Show resolved Hide resolved
tool/tbot/tshwrap/wrap.go Outdated Show resolved Hide resolved
tool/tbot/tshwrap/wrap.go Outdated Show resolved Hide resolved
tool/tbot/tshwrap/wrap.go Show resolved Hide resolved
tool/tbot/tshwrap/wrap.go Outdated Show resolved Hide resolved
timothyb89 and others added 2 commits May 27, 2022 10:44
Co-authored-by: Alan Parra <alan.parra@goteleport.com>
@timothyb89 timothyb89 enabled auto-merge (squash) May 27, 2022 17:24
@timothyb89 timothyb89 merged commit 8f36b9c into master May 27, 2022
timothyb89 added a commit that referenced this pull request May 27, 2022
* Extend support for identity files in tsh

This enhances support for identity files in tsh, which previously only
worked for regular SSH access. The largest blocker for support is that
tsh uses profiles for all non-SSH resource access, and profiles have a
direct mapping to some on-disk resources. This patch works around this
in a few ways:
 * Virtual profiles: When an identity file is specified with `-i`, we
   use it to create an in-memory virtual profile using the cert as the
   root identity _and_ for every `RouteToDatabase` (and in the future,
   app) field contained in the cert.
 * Virtual profile paths: Certain profile operations require paths to
   valid certificates and other resources on disk, which may not exist
   inside the identity file.

   As the driving use-case for this change is integration with Machine
   ID, we can "cheat" and pass the correct paths to tsh via
   environment variables. A cooperating wrapper in `tbot` will execute
   `tsh` with appropriate flags and environment variables, which
   override tsh's usual certifiate paths. This allows commands like
   `tsh db connect ...` to work as expected.
 * Key stores: previously we used a `noLocalKeyStore{}` with which all
   lookups fail. This patch replaces it with an in-memory keystore if
   a client key is available.
 * Profile status: lastly, we add a new `StatusCurrentWithIdentity()`
   function to load virtual profiles where supported. Some commands
   are not supported in this PR (like `tsh app ...`), but others
   don't make sense to support (like cert reissuing).

   We might consider merging everything into the traditional
   `StatusCurrent()` when adding app support.

App access is still broken and will be addressed in a later change.

Partially fixes #11770

* Fix failing lint

* Add `tbot proxy` and `tbot db` wrapper commands

This adds new wrapper commands that leverage tsh for proxy and
database access.

It also adds a new `tshwrap` helper package which contains utilities
for locating the tsh executable, checking its version, and loading
all necessary data (certificates, destinations, etc) that will need
to be passed to tsh for wrapped commands to function.

* Fix failing unit test due to incorrect default IsVirtual profile flag

* Combine `StatusCurrentWithIdentity()` into `StatusCurrent()`

Additionally, log a warning when environment variable paths aren't
found.

* Fix virtual profile flag always being true

* Update lib/client/api.go

Co-authored-by: Krzysztof Skrzętnicki <krzysztof.skrzetnicki@goteleport.com>

* Address review feedback

* Use `tbot proxy` in generated `ssh_config`

* Add tests for mockable parts of our tsh integration

* Fix lints

* Clarify docstrings in CLIConf

* Tweak comment for clarity; fix typo in `onProxyCommand`

* Add missing copyright header

* Fix failing unit test and pass destination to `Describe()`

This fixes a failing unit test by making the description for
`ssh_config` match its behavior in practice. This necessitated
passing the destination to all templates, unfortunately.

* Add a few extra tests

* Apply suggestions from code review

Co-authored-by: Alan Parra <alan.parra@goteleport.com>

* Address another batch of review comments

* Comment tweaks

* Refactor tshwrap to remove the Runner interface.

* Apply suggestions from code review

Co-authored-by: Alan Parra <alan.parra@goteleport.com>

* Address review comments

Co-authored-by: Krzysztof Skrzętnicki <krzysztof.skrzetnicki@goteleport.com>
Co-authored-by: Alan Parra <alan.parra@goteleport.com>
timothyb89 added a commit that referenced this pull request May 27, 2022
* Extend support for identity files in tsh

This enhances support for identity files in tsh, which previously only
worked for regular SSH access. The largest blocker for support is that
tsh uses profiles for all non-SSH resource access, and profiles have a
direct mapping to some on-disk resources. This patch works around this
in a few ways:
 * Virtual profiles: When an identity file is specified with `-i`, we
   use it to create an in-memory virtual profile using the cert as the
   root identity _and_ for every `RouteToDatabase` (and in the future,
   app) field contained in the cert.
 * Virtual profile paths: Certain profile operations require paths to
   valid certificates and other resources on disk, which may not exist
   inside the identity file.

   As the driving use-case for this change is integration with Machine
   ID, we can "cheat" and pass the correct paths to tsh via
   environment variables. A cooperating wrapper in `tbot` will execute
   `tsh` with appropriate flags and environment variables, which
   override tsh's usual certifiate paths. This allows commands like
   `tsh db connect ...` to work as expected.
 * Key stores: previously we used a `noLocalKeyStore{}` with which all
   lookups fail. This patch replaces it with an in-memory keystore if
   a client key is available.
 * Profile status: lastly, we add a new `StatusCurrentWithIdentity()`
   function to load virtual profiles where supported. Some commands
   are not supported in this PR (like `tsh app ...`), but others
   don't make sense to support (like cert reissuing).

   We might consider merging everything into the traditional
   `StatusCurrent()` when adding app support.

App access is still broken and will be addressed in a later change.

Partially fixes #11770

* Fix failing lint

* Add `tbot proxy` and `tbot db` wrapper commands

This adds new wrapper commands that leverage tsh for proxy and
database access.

It also adds a new `tshwrap` helper package which contains utilities
for locating the tsh executable, checking its version, and loading
all necessary data (certificates, destinations, etc) that will need
to be passed to tsh for wrapped commands to function.

* Fix failing unit test due to incorrect default IsVirtual profile flag

* Combine `StatusCurrentWithIdentity()` into `StatusCurrent()`

Additionally, log a warning when environment variable paths aren't
found.

* Fix virtual profile flag always being true

* Update lib/client/api.go

Co-authored-by: Krzysztof Skrzętnicki <krzysztof.skrzetnicki@goteleport.com>

* Address review feedback

* Use `tbot proxy` in generated `ssh_config`

* Add tests for mockable parts of our tsh integration

* Fix lints

* Clarify docstrings in CLIConf

* Tweak comment for clarity; fix typo in `onProxyCommand`

* Add missing copyright header

* Fix failing unit test and pass destination to `Describe()`

This fixes a failing unit test by making the description for
`ssh_config` match its behavior in practice. This necessitated
passing the destination to all templates, unfortunately.

* Add a few extra tests

* Apply suggestions from code review

Co-authored-by: Alan Parra <alan.parra@goteleport.com>

* Address another batch of review comments

* Comment tweaks

* Refactor tshwrap to remove the Runner interface.

* Apply suggestions from code review

Co-authored-by: Alan Parra <alan.parra@goteleport.com>

* Address review comments

Co-authored-by: Krzysztof Skrzętnicki <krzysztof.skrzetnicki@goteleport.com>
Co-authored-by: Alan Parra <alan.parra@goteleport.com>

Co-authored-by: Krzysztof Skrzętnicki <krzysztof.skrzetnicki@goteleport.com>
Co-authored-by: Alan Parra <alan.parra@goteleport.com>
@webvictim webvictim mentioned this pull request Jun 8, 2022
@zmb3 zmb3 deleted the timothyb89/tbot-proxy-wrappers branch April 26, 2023 21:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Machine ID ssh_config requires legacy SSH handler to be enabled
4 participants