-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Refactor lib/teleterm gateway #27685
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -18,13 +18,25 @@ package clusters | |||||||||||||
|
|
||||||||||||||
| import ( | ||||||||||||||
| "context" | ||||||||||||||
| "crypto/tls" | ||||||||||||||
|
|
||||||||||||||
| "github.com/gravitational/trace" | ||||||||||||||
|
|
||||||||||||||
| "github.com/gravitational/teleport/lib/client/db/dbcmd" | ||||||||||||||
| "github.com/gravitational/teleport/lib/teleterm/gateway" | ||||||||||||||
| "github.com/gravitational/teleport/lib/tlsca" | ||||||||||||||
| ) | ||||||||||||||
|
|
||||||||||||||
| // ReissueCertFunc is a callback function for Cluster to actually do the issue | ||||||||||||||
| // of user certificates with TeleportClient. | ||||||||||||||
| type ReissueCertFunc func(context.Context) error | ||||||||||||||
|
|
||||||||||||||
| // GatewayCertReissuer defines an interface of a helper that manages the | ||||||||||||||
| // process of reissuing certificates. | ||||||||||||||
| type GatewayCertReissuer interface { | ||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. seems that is used in scope of this package so we can make it enexported:
Suggested change
|
||||||||||||||
| ReissueCert(ctx context.Context, gateway *gateway.Gateway, doReissueCert ReissueCertFunc) error | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| type CreateGatewayParams struct { | ||||||||||||||
| // TargetURI is the cluster resource URI | ||||||||||||||
| TargetURI string | ||||||||||||||
|
|
@@ -36,12 +48,15 @@ type CreateGatewayParams struct { | |||||||||||||
| // LocalPort is the gateway local port | ||||||||||||||
| LocalPort string | ||||||||||||||
| CLICommandProvider gateway.CLICommandProvider | ||||||||||||||
| TCPPortAllocator gateway.TCPPortAllocator | ||||||||||||||
| OnExpiredCert gateway.OnExpiredCertFunc | ||||||||||||||
| CertReissuer GatewayCertReissuer | ||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: go docs. |
||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| // CreateGateway creates a gateway | ||||||||||||||
| func (c *Cluster) CreateGateway(ctx context.Context, params CreateGatewayParams) (*gateway.Gateway, error) { | ||||||||||||||
| if params.CLICommandProvider == nil { | ||||||||||||||
| params.CLICommandProvider = NewDbcmdCLICommandProvider(c, dbcmd.SystemExecer{}) | ||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How does moving My original comment pointed that the creation of the CLI command provider should be moved up to outer layers, not further down. Instead of creating a new struct for every gateway, we'd have only two CLI command provider structs, say This goes back to how dependencies are typically injected in Go and how we should approach mocking them out in tests. I'm no expert on this, so by all means please push back on this if you disagree! I'm not sure if any of this matters to be honest. Because of how This is clearly demonstrated by
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Now that I think about it,
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I asked for some opinions on Slack regarding passing the deps up. https://gravitational.slack.com/archives/C08HF8E9F/p1686740351896489
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In my opinion,
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But I think the same problem applies to the way I am doing the reissue cert callback. Do we need the storage to get a fresh Cluster for reissuing the database cert? Seems so right? so the current refactor on issue cert won't work. Let me rethink through this.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. wonder if we can just have a
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure if a fresh cluster is needed for the db cert, but if we're to have AFAIK,
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I think it's safer to get a fresh cluster for reissuing db cert the same way a fresh cluster is used for DBCliCommandProvider. If so, I would say the current implemented way is better so refactoring is probably not needed at all =D. Thinking through this, I found a problem with this change I made earlier: teleport/lib/teleterm/gateway/config.go Lines 77 to 82 in 0bd2517
So the gateway is caching |
||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| db, err := c.GetDatabase(ctx, params.TargetURI) | ||||||||||||||
| if err != nil { | ||||||||||||||
| return nil, trace.Wrap(err) | ||||||||||||||
|
|
@@ -53,7 +68,8 @@ func (c *Cluster) CreateGateway(ctx context.Context, params CreateGatewayParams) | |||||||||||||
| Username: params.TargetUser, | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| if err := c.ReissueDBCerts(ctx, routeToDatabase); err != nil { | ||||||||||||||
| tlsCert, err := c.reissueAndLoadDBCert(ctx, routeToDatabase) | ||||||||||||||
| if err != nil { | ||||||||||||||
| return nil, trace.Wrap(err) | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
|
|
@@ -63,15 +79,14 @@ func (c *Cluster) CreateGateway(ctx context.Context, params CreateGatewayParams) | |||||||||||||
| TargetUser: params.TargetUser, | ||||||||||||||
| TargetName: db.GetName(), | ||||||||||||||
| TargetSubresourceName: params.TargetSubresourceName, | ||||||||||||||
| Cert: tlsCert, | ||||||||||||||
| Protocol: db.GetProtocol(), | ||||||||||||||
| KeyPath: c.status.KeyPath(), | ||||||||||||||
| CertPath: c.status.DatabaseCertPathForCluster(c.clusterClient.SiteName, db.GetName()), | ||||||||||||||
| Insecure: c.clusterClient.InsecureSkipVerify, | ||||||||||||||
| WebProxyAddr: c.clusterClient.WebProxyAddr, | ||||||||||||||
| Log: c.Log, | ||||||||||||||
| CLICommandProvider: params.CLICommandProvider, | ||||||||||||||
| TCPPortAllocator: params.TCPPortAllocator, | ||||||||||||||
| OnExpiredCert: params.OnExpiredCert, | ||||||||||||||
| TCPPortAllocator: gateway.NetTCPPortAllocator{}, | ||||||||||||||
| ReissueCert: c.makeGatewayReissueDBCertFunc(params.CertReissuer, routeToDatabase), | ||||||||||||||
| Clock: c.clock, | ||||||||||||||
| TLSRoutingConnUpgradeRequired: c.clusterClient.TLSRoutingConnUpgradeRequired, | ||||||||||||||
| RootClusterCACertPoolFunc: c.clusterClient.RootClusterCACertPool, | ||||||||||||||
|
|
@@ -82,3 +97,20 @@ func (c *Cluster) CreateGateway(ctx context.Context, params CreateGatewayParams) | |||||||||||||
|
|
||||||||||||||
| return gw, nil | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| // makeGatewayReissueDBCertFunc creates a gateway.ReissueCertFunc that reissues | ||||||||||||||
| // the database certificate using provided GatewayCertReissuer, then loads the | ||||||||||||||
| // certificate. | ||||||||||||||
| func (c *Cluster) makeGatewayReissueDBCertFunc(certReissuer GatewayCertReissuer, routeToDatabase tlsca.RouteToDatabase) gateway.ReissueCertFunc { | ||||||||||||||
| return func(ctx context.Context, gateway *gateway.Gateway) (tls.Certificate, error) { | ||||||||||||||
| err := certReissuer.ReissueCert(ctx, gateway, func(ctx context.Context) error { | ||||||||||||||
| return trace.Wrap(c.reissueDBCerts(ctx, routeToDatabase)) | ||||||||||||||
| }) | ||||||||||||||
| if err != nil { | ||||||||||||||
| return tls.Certificate{}, trace.Wrap(err) | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| tlsCert, err := c.loadDBCert(routeToDatabase) | ||||||||||||||
| return tlsCert, trace.Wrap(err) | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -27,42 +27,33 @@ import ( | |
| // DbcmdCLICommandProvider provides CLI commands for database gateways. It needs Storage to read | ||
| // fresh profile state from the disk. | ||
| type DbcmdCLICommandProvider struct { | ||
| storage StorageByResourceURI | ||
| cluster *Cluster | ||
| execer dbcmd.Execer | ||
| } | ||
|
|
||
| type StorageByResourceURI interface { | ||
| GetByResourceURI(string) (*Cluster, error) | ||
| } | ||
|
|
||
| func NewDbcmdCLICommandProvider(storage StorageByResourceURI, execer dbcmd.Execer) DbcmdCLICommandProvider { | ||
| func NewDbcmdCLICommandProvider(cluster *Cluster, execer dbcmd.Execer) DbcmdCLICommandProvider { | ||
| return DbcmdCLICommandProvider{ | ||
| storage: storage, | ||
| cluster: cluster, | ||
| execer: execer, | ||
| } | ||
| } | ||
|
|
||
| func (d DbcmdCLICommandProvider) GetCommand(gateway *gateway.Gateway) (*exec.Cmd, error) { | ||
| cluster, err := d.storage.GetByResourceURI(gateway.TargetURI()) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think I originally made it so that Do you foresee any problems with essentially "caching" it in a long-lived object such as the gateway? I'm not totally sure how Same goes for
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When connecting to the local proxy (tunnel mode), One exception to above is Oracle where So maybe I can refactor
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This might be another argument for returning a fresh |
||
| if err != nil { | ||
| return nil, trace.Wrap(err) | ||
| } | ||
|
|
||
| routeToDb := tlsca.RouteToDatabase{ | ||
| ServiceName: gateway.TargetName(), | ||
| Protocol: gateway.Protocol(), | ||
| Username: gateway.TargetUser(), | ||
| Database: gateway.TargetSubresourceName(), | ||
| } | ||
|
|
||
| cmd, err := dbcmd.NewCmdBuilder(cluster.clusterClient, &cluster.status, routeToDb, | ||
| cmd, err := dbcmd.NewCmdBuilder(d.cluster.clusterClient, &d.cluster.status, routeToDb, | ||
| // TODO(ravicious): Pass the root cluster name here. cluster.Name returns leaf name for leaf | ||
| // clusters. | ||
| // | ||
| // At this point it doesn't matter though because this argument is used only for | ||
| // generating correct CA paths. We use dbcmd.WithNoTLS here which means that the CA paths aren't | ||
| // included in the returned CLI command. | ||
| cluster.Name, | ||
| d.cluster.Name, | ||
| dbcmd.WithLogger(gateway.Log()), | ||
| dbcmd.WithLocalProxy(gateway.LocalAddress(), gateway.LocalPortInt(), ""), | ||
| dbcmd.WithNoTLS(), | ||
|
|
||
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.
the
LoadDBCertis always called afterreissueDBCertssince thereissueDBCertsuses the same information to issue a new cert at first glance therouteToDatabase.CheckCertSubjectvalidation inside loadDBCert function is not needed.nit: Also instead of loadingCert from disc directly we can use the introduce a helper function for our local agent
c.clusterClient.LocalAgent()that will be responsible for loading the cert.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.
Checking the cert subject after calling
reissueDBCertswas originally added becausereissueDBCertssaves the cert to disk without returning it from the function. As such, we need to load the cert then and there's a small chance that between saving and loading, the cert on disk might be modified by e.g.tsh db loginbeing ran from a separate shell session. See #18740.After we switch from using
ReissueUserCertstoIssueUserCertsWithMFA(which returns the cert), checking the cert subject will become unnecessary.