mirror of
https://github.com/gravitational/teleport.git
synced 2026-10-25 10:40:02 +08:00
* Cherry-pick in-band MFA support changes for VNet that do no depend on x/crypto/ssh. Signed-off-by: Chris Thach <chris.thach@goteleport.com> * Use cached client. Signed-off-by: Chris Thach <chris.thach@goteleport.com> * Migrate to mfav2 after rebase. Signed-off-by: Chris Thach <chris.thach@goteleport.com> --------- Signed-off-by: Chris Thach <chris.thach@goteleport.com>
200 lines
8.5 KiB
Go
200 lines
8.5 KiB
Go
// Teleport
|
|
// Copyright (C) 2025 Gravitational, Inc.
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Affero General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Affero General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
package vnet
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"os"
|
|
|
|
"github.com/gravitational/trace"
|
|
"github.com/jonboulle/clockwork"
|
|
|
|
apiutils "github.com/gravitational/teleport/api/utils"
|
|
vnetv1 "github.com/gravitational/teleport/gen/proto/go/teleport/lib/vnet/v1"
|
|
"github.com/gravitational/teleport/lib/auth/authclient"
|
|
"github.com/gravitational/teleport/lib/client"
|
|
)
|
|
|
|
// ClientApplication is the common interface implemented by each VNet client
|
|
// application: Connect and tsh. It provides methods to list user profiles, get
|
|
// cluster clients, issue app certificates, and report metrics and errors -
|
|
// anything that uses the user's credentials or a Teleport client.
|
|
// The name "client application" refers to a user-facing client application, in
|
|
// contrast to the MacOS daemon or Windows service.
|
|
type ClientApplication interface {
|
|
// ListProfiles lists the names of all profiles saved for the user.
|
|
ListProfiles() ([]string, error)
|
|
|
|
// GetCachedClient returns a [*client.ClusterClient] for the given profile and leaf cluster.
|
|
// [leafClusterName] may be empty when requesting a client for the root cluster. Returned clients are
|
|
// expected to be cached, as this may be called frequently.
|
|
GetCachedClient(ctx context.Context, profileName, leafClusterName string) (ClusterClient, error)
|
|
|
|
// ReissueAppCert issues a new cert for the target app.
|
|
ReissueAppCert(ctx context.Context, appInfo *vnetv1.AppInfo, targetPort uint16) (tls.Certificate, error)
|
|
|
|
// UserTLSCert returns the user TLS certificate for the given profile.
|
|
UserTLSCert(ctx context.Context, profileName string) (tls.Certificate, error)
|
|
|
|
// GetDialOptions returns ALPN dial options for the profile.
|
|
GetDialOptions(ctx context.Context, profileName string) (*vnetv1.DialOptions, error)
|
|
|
|
// OnNewSSHSession should be called whenever a new SSH session is about to be
|
|
// started, after getting the user SSH certificate for the session.
|
|
OnNewSSHSession(ctx context.Context, profileName, rootClusterName string)
|
|
|
|
// OnNewAppConnection gets called whenever a new app connection is about to be established through VNet.
|
|
// By the time OnNewAppConnection, VNet has already verified that the user holds a valid cert for the
|
|
// app.
|
|
//
|
|
// The connection won't be established until OnNewAppConnection returns. Returning an error prevents
|
|
// the connection from being made.
|
|
OnNewAppConnection(ctx context.Context, appKey *vnetv1.AppKey) error
|
|
|
|
// OnInvalidLocalPort gets called before VNet refuses to handle a connection to a multi-port TCP app
|
|
// because the provided port does not match any of the TCP ports in the app spec.
|
|
OnInvalidLocalPort(ctx context.Context, appInfo *vnetv1.AppInfo, targetPort uint16)
|
|
|
|
// ReissueDBCert issues a new cert for the target database.
|
|
ReissueDBCert(ctx context.Context, dbInfo *vnetv1.DatabaseInfo) (tls.Certificate, error)
|
|
|
|
// OnNewDBConnection gets called whenever a new database connection is about to be established
|
|
// through VNet. By the time OnNewDBConnection is called, VNet has already verified that the user
|
|
// holds a valid cert for the database.
|
|
//
|
|
// The connection won't be established until OnNewDBConnection returns. Returning an error prevents
|
|
// the connection from being made.
|
|
OnNewDBConnection(ctx context.Context, dbKey *vnetv1.DatabaseKey) error
|
|
}
|
|
|
|
// ClusterClient is an interface defining the subset of [client.ClusterClient]
|
|
// methods used by via [ClientApplication].
|
|
type ClusterClient interface {
|
|
CurrentCluster() authclient.ClientI
|
|
ClusterName() string
|
|
RootClusterName() string
|
|
SessionSSHKeyRing(ctx context.Context, user string, target client.NodeDetails) (keyRing *client.KeyRing, completedMFA bool, err error)
|
|
PerformSessionMFACeremony(ctx context.Context, sessionID []byte) (challengeName string, err error)
|
|
}
|
|
|
|
// RunUserProcess is the entry point called by all VNet client applications
|
|
// (tsh, Connect) to start and run all VNet tasks.
|
|
//
|
|
// ctx is used for setup steps that happen before RunUserProcess passes control
|
|
// to the process manager. Canceling ctx after RunUserProcess returns will _not_
|
|
// cancel the background tasks.
|
|
//
|
|
// If [RunUserProcess] returns without error the caller is expected to
|
|
// eventually call Close on the UserProcess to clean up any resources, terminate
|
|
// all child processes, and remove any OS configuration used for actively
|
|
// running VNet. Callers should also call Wait to get notified if the process
|
|
// terminates early due to an error.
|
|
func RunUserProcess(ctx context.Context, clientApplication ClientApplication) (*UserProcess, error) {
|
|
clock := clockwork.NewRealClock()
|
|
clusterConfigCache := NewClusterConfigCache(clock)
|
|
leafClusterCache, err := newLeafClusterCache(clock)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
// TODO(greedy52) VNet config may have a flag like `allow_app_https_tunnel`
|
|
// to opt-in this feature once browser support is added for app HTTPS
|
|
// tunnel. Using an unstable env var for testing purpose for now.
|
|
allowAppHTTPSTunnel, _ := apiutils.ParseBool(os.Getenv("TELEPORT_UNSTABLE_VNET_APP_HTTPS_TUNNEL"))
|
|
if allowAppHTTPSTunnel {
|
|
log.InfoContext(ctx, "App HTTPS tunnel is enabled")
|
|
}
|
|
fqdnResolver := newFQDNResolver(&fqdnResolverConfig{
|
|
clientApplication: clientApplication,
|
|
clusterConfigCache: clusterConfigCache,
|
|
leafClusterCache: leafClusterCache,
|
|
// DB access via VNet is currently only supported by the tbot beams, but
|
|
// disabled for tsh/Connect by default. flip to true to enable DB access via VNet
|
|
// for tsh/Connect to validate locally.
|
|
allowDatabaseAccess: false,
|
|
allowAppHTTPSTunnel: allowAppHTTPSTunnel,
|
|
})
|
|
unifiedClusterConfigProvider := NewUnifiedClusterConfigProvider(&UnifiedClusterConfigProviderConfig{
|
|
clientApplication: clientApplication,
|
|
clusterConfigCache: clusterConfigCache,
|
|
leafClusterCache: leafClusterCache,
|
|
})
|
|
clientApplicationService, err := newClientApplicationService(&clientApplicationServiceConfig{
|
|
clientApplication: clientApplication,
|
|
fqdnResolver: fqdnResolver,
|
|
unifiedClusterConfigProvider: unifiedClusterConfigProvider,
|
|
clock: clock,
|
|
})
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
processManager, processCtx := newProcessManager()
|
|
sshConfigurator := newSSHConfigurator(sshConfiguratorConfig{
|
|
clientApplication: clientApplication,
|
|
leafClusterCache: leafClusterCache,
|
|
})
|
|
processManager.AddCriticalBackgroundTask("SSH configuration loop", func() error {
|
|
return trace.Wrap(sshConfigurator.runConfigurationLoop(processCtx))
|
|
})
|
|
|
|
userProcess := &UserProcess{
|
|
clientApplication: clientApplication,
|
|
unifiedClusterConfigProvider: unifiedClusterConfigProvider,
|
|
clientApplicationService: clientApplicationService,
|
|
clock: clock,
|
|
processManager: processManager,
|
|
}
|
|
if err := userProcess.runPlatformUserProcess(processCtx); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return userProcess, nil
|
|
}
|
|
|
|
// UserProcess holds the state of the VNet user process, see the comment on
|
|
// [RunUserProcess] for usage details.
|
|
type UserProcess struct {
|
|
clientApplication ClientApplication
|
|
|
|
clock clockwork.Clock
|
|
unifiedClusterConfigProvider *UnifiedClusterConfigProvider
|
|
clientApplicationService *clientApplicationService
|
|
|
|
processManager *ProcessManager
|
|
networkStackInfo *vnetv1.NetworkStackInfo
|
|
}
|
|
|
|
func (p *UserProcess) Close() {
|
|
p.processManager.Close()
|
|
}
|
|
|
|
func (p *UserProcess) Wait() error {
|
|
return p.processManager.Wait()
|
|
}
|
|
|
|
func (p *UserProcess) NetworkStackInfo() *vnetv1.NetworkStackInfo {
|
|
return p.networkStackInfo
|
|
}
|
|
|
|
// GetTargetOSConfiguration returns the LocalOSConfigProvider which clients may
|
|
// use to report the proxied DNS zones, run diagnostics, etc. The returned
|
|
// *LocalOSConfigProvider will remain valid even if the UserProcess is closed.
|
|
func (p *UserProcess) GetUnifiedClusterConfigProvider() *UnifiedClusterConfigProvider {
|
|
return p.unifiedClusterConfigProvider
|
|
}
|