-
Notifications
You must be signed in to change notification settings - Fork 1.8k
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
Extend support for identity files in tsh #12686
Conversation
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
Can we use |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A couple of high-level comments. In general the concept of "virtual profiles" looks complex enough to benefit from RFD - we are adding a lot of complexity here which will be costly in the long run.
tool/tsh/db.go
Outdated
|
||
// Note: we don't use StatusCurrentWithIdentity here as virtual identity | ||
// profiles are read-only. | ||
profile, err := client.StatusCurrentWithIdentity(cf.HomePath, cf.Proxy, cf.IdentityFileIn) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it make sense to separate the concept of read-only profile from identity-based profile? Right now both are expressed with catch-all "virtual" designation, but it feels like a leaky abstraction.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It might, though I'm not sure what that use-case would be. As far as I know the only place read-only profiles make sense is identity-based profiles: there's no other way to get a certificate that has database/app/k8s/etc fields, and tsh only writes to request extra permissions for a particular database/app/etc.
Either you go the usual way through tsh (which is inherently read/write), or you use tctl auth sign ...
/ Machine ID in which case you bring an identity file with whichever additional fields you'd like. A read-only tsh profile wouldn't be able to request e.g. database certs in a very useful way.
return trace.Wrap(err) | ||
// Identity files contain themselves act as the database credentials (if | ||
// any), so don't bother fetching new certs. | ||
if profile.IsVirtual { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a way to restructure the code so that we don't increase the complexity massively by having each and every operation support "virtual profiles" in explicit way?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't much like the verbosity either, but ultimately certain codepaths (like this one) do things that will fail when given an identity file. Arguably when using an identity file I don't think any additional certs should get written to disk, and moreover RetryWithRelogin()
and IssueUserCertsWithMFA()
will fail on Machine ID identity files as they're explicitly non-renewable.
If we (per your comment below) made RetryWithRelogin
and friends identity-aware (i.e. effectively a no-op), we might be able to skip more of these checks. It's hard for me to predict if that'd be significantly simpler, though.
(and wow, that comment is unreadable, whoops...)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With the latest change I've merged everything into the one codepath (StatusCurrent()
) and we have a total of 3 IsVirtual
checks for core + database access.
I know app (and probably k8s) access will need some of their own when we circle back to support them properly, but I don't think this is especially onerous - worst case if some specific handling is missing it fails due to some identity-related error exactly as it would already today.
Overall, though, I think the alternative where we modify RetryWithRelogin
to support identity files ends up quite a bit more complicated and I'd rather direct that effort to a proper refactoring where hacks like this aren't needed at all.
log.Info("Note: already logged in due to an identity file (`-i ...`); will only update database config files.") | ||
} else { | ||
var key *client.Key | ||
if err = client.RetryWithRelogin(cf.Context, tc, func() error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we perhaps add virtual (read-only?) profile handling to RetryWithLogin
to have uniform support across the various ops?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd like to give this a deeper look. My gut feeling is that it'd be similarly verbose: everything that issues certs would need to see the identity file, skip reissuing certs, and then store to memory rather than disk. We'd still have issues with profiles and especially the paths that get passed to e.g. database CLIs that are expected to be on disk.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've looked a bit more, and I don't think we really save anything here: RetryWithRelogin()
(and more specifically, client.Login()
) just manages the core client identity which we already configure with the identity file at startup.
Database / Kubernetes / App access all use profile certs and never try to use the core identity even if it does actually have the proper permissions. Unfortunately the only shared codepath for these is the profiles system, which is what necessitated this hacky solution to begin with.
Ideally I'd like to see us invest in a larger refactoring to clean all of this up but frankly I don't even know what I'd want it to look like, tsh has a lot of functionality to juggle. In the mean time hopefully we can enable at least the Machine ID use-case.
tool/tsh/db.go
Outdated
@@ -275,7 +292,7 @@ func onDatabaseConfig(cf *CLIConf) error { | |||
if err != nil { | |||
return trace.Wrap(err) | |||
} | |||
profile, err := client.StatusCurrent(cf.HomePath, cf.Proxy) | |||
profile, err := client.StatusCurrentWithIdentity(cf.HomePath, cf.Proxy, cf.IdentityFileIn) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seeing how many instances of StatusCurrent
had to be updated I wonder if we can have a helper method taking in CLIConf instead? This way we can ensure consistency across the board and reduce the risk of someone using the wrong method (without cf.IdentityFileIn
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could also merge identity file support into StatusCurrent()
itself and have just the one function. It would mean adding a profile.IsVirtual
check afterward in the few places that can't support virtual profiles (mainly anything that reissues certs).
I figured it'd be safer to keep the old codepath where (currently) unsupported, but it's an easy change to make.
There was a problem hiding this comment.
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 merged all the functionality into StatusCurrent()
. We need to pass along the identity path but it's easily available everywhere, so I think this should be modestly nicer overall.
To be honest I wasn't aware of that new functionality - it's definitely worth a look. I fear it'll have similar limitations for our needs on the Machine ID front, though. We specifically need the proxying functionality and without changes none of these tsh codepaths will simply trust an external identity without trying to do problematic things (like fetching new certs or reading/writing files). |
Yes, I think the changes will be needed, I just figured the wrapper looks similar to |
I've given the daemon functionality a quick glance and my takeaway so far is: I definitely like the gRPC interface and it could be a big help for Machine ID's needs in the future, but doesn't really provide any of the functionality we want at the moment (mainly That said, since our main supported interface to tsh will be through CLI wrappers in tbot ( Timeline-wise we're in a bit of a awkward spot with access capabilities in Machine ID: we don't support Teleport's new out-of-the-box configuration since everything's now behind TLS routing by default, which requires a local proxy. While we can issue certs for databases they're unusable currently, and even SSH needs awkward workarounds. So, there's a desire to get some functionality in in the near term and I'm not opposed to replacing it with something better in the long/medium-term. |
Additionally, log a warning when environment variable paths aren't found.
…nded-identityfiles
type VirtualPathParams []string | ||
|
||
// VirtualPathCAParams returns parameters for selecting CA certificates. | ||
func VirtualPathCAParams(caType types.CertAuthType) VirtualPathParams { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this needs to be kept in sync with the CA types I would put a comment in both locations mentioning that any updates need to be reflected on the other side.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oddly tsh only ever uses the HostCA. I do kind of wonder if this is a problem for new clusters since I think the database CA is meant to be used to access databases in 9.0+.
In any case the only thing that would need to be updated is on Machine ID's end if/when we ever needed to pass multiple CAs. Right now we're just using the fallback case (just TSH_VIRTUAL_PATH_CA
, with no CA type) in #12687 since we don't care to disambiguate (at least for now).
lib/client/api.go
Outdated
// and returns a *ProfileStatus which can be used to print the status of the | ||
// profile. | ||
func ReadProfileStatus(profileDir string, profileName string) (*ProfileStatus, error) { | ||
var err error |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you remove this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Assuming you mean the err
, yup, will do.
lib/client/api.go
Outdated
@@ -1192,7 +1408,36 @@ func NewClient(c *Config) (tc *TeleportClient, err error) { | |||
// if the client was passed an agent in the configuration and skip local auth, use | |||
// the passed in agent. | |||
if c.Agent != nil { | |||
tc.localAgent = &LocalKeyAgent{Agent: c.Agent, keyStore: noLocalKeyStore{}, siteName: tc.SiteName} | |||
webProxyHost, _ := tc.WebProxyHostPort() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the _ that you're ignoring here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's the port (int). That said we also have a WebProxyHost()
helper that does exactly this, so I'll switch to it.
Co-authored-by: Krzysztof Skrzętnicki <[email protected]>
…nded-identityfiles
* 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 * 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 <[email protected]> * Address review feedback Co-authored-by: Krzysztof Skrzętnicki <[email protected]>
* 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 * 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 <[email protected]> * Address review feedback Co-authored-by: Krzysztof Skrzętnicki <[email protected]> Co-authored-by: Krzysztof Skrzętnicki <[email protected]>
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 everyRouteToDatabase
(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 executetsh
with appropriate flags and environment variables, which override tsh's usual certifiate paths. This allows commands liketsh 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 modify
StatusCurrent()
to accept an identity file path and load virtual profiles when set.This particular change is primarily targets database support, but lays the groundwork for app and Kubernetes support. We'll work on support for them in future PRs.
Partially fixes #11770