Skip to content

Commit

Permalink
[vnet] refactor AppProvider for Windows implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
nklaassen committed Jan 18, 2025
1 parent 049d987 commit 0fc5882
Show file tree
Hide file tree
Showing 14 changed files with 567 additions and 466 deletions.
23 changes: 23 additions & 0 deletions lib/client/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -5292,6 +5292,29 @@ func (tc *TeleportClient) RootClusterCACertPool(ctx context.Context) (*x509.Cert
return pool, trace.Wrap(err)
}

// RootClusterCACertPoolPEM returns a PEM-encoded cert pool with the root cluster CA.
func (tc *TeleportClient) RootClusterCACertPoolPEM(ctx context.Context) ([]byte, error) {
_, span := tc.Tracer.Start(
ctx,
"teleportClient/RootClusterCACertPoolPEM",
oteltrace.WithSpanKind(oteltrace.SpanKindClient),
)
defer span.End()

keyRing, err := tc.localAgent.GetCoreKeyRing()
if err != nil {
return nil, trace.Wrap(err)
}

rootClusterName, err := keyRing.RootClusterName()
if err != nil {
return nil, trace.Wrap(err)
}

pool, err := keyRing.clientCertPoolPEM(rootClusterName)
return pool, trace.Wrap(err)
}

// HeadlessApprove handles approval of a headless authentication request.
func (tc *TeleportClient) HeadlessApprove(ctx context.Context, headlessAuthenticationID string, confirm bool) error {
ctx, span := tc.Tracer.Start(
Expand Down
31 changes: 23 additions & 8 deletions lib/client/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
"crypto/x509"
"fmt"
"log/slog"
"slices"
"strings"
"time"

Expand Down Expand Up @@ -303,21 +304,35 @@ func (k *KeyRing) clientTLSConfig(cipherSuites []uint16, cred TLSCredential, clu

// ClientCertPool returns x509.CertPool containing trusted CA.
func (k *KeyRing) clientCertPool(clusters ...string) (*x509.CertPool, error) {
certPoolPEM, err := k.clientCertPoolPEM(clusters...)
if err != nil {
return nil, trace.Wrap(err)
}
pool := x509.NewCertPool()
if !pool.AppendCertsFromPEM(certPoolPEM) {
return nil, trace.BadParameter("failed to parse TLS CA certificate")
}
return pool, nil
}

func (k *KeyRing) clientCertPoolPEM(clusters ...string) ([]byte, error) {
var certPoolPEM bytes.Buffer
for _, caPEM := range k.TLSCAs() {
cert, err := tlsca.ParseCertificatePEM(caPEM)
if err != nil {
return nil, trace.Wrap(err)
return nil, trace.Wrap(err, "parsing TLS CA certificate")
}
for _, k := range clusters {
if cert.Subject.CommonName == k {
if !pool.AppendCertsFromPEM(caPEM) {
return nil, trace.BadParameter("failed to parse TLS CA certificate")
}
}
if !slices.Contains(clusters, cert.Subject.CommonName) {
continue
}
certPoolPEM.Write(caPEM)
// PEM files should end with a trailing newline, just double check
// before potentially concatenating multiple together.
if caPEM[len(caPEM)-1] != '\n' {
certPoolPEM.WriteByte('\n')
}
}
return pool, nil
return certPoolPEM.Bytes(), nil
}

// ProxyClientSSHConfig returns an ssh.ClientConfig with SSH credentials from this
Expand Down
66 changes: 37 additions & 29 deletions lib/teleterm/vnet/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,11 @@ import (
"google.golang.org/protobuf/types/known/timestamppb"

"github.com/gravitational/teleport"
"github.com/gravitational/teleport/api/client/proto"
"github.com/gravitational/teleport/api/types"
"github.com/gravitational/teleport/api/utils"
prehogv1alpha "github.com/gravitational/teleport/gen/proto/go/prehog/v1alpha"
apiteleterm "github.com/gravitational/teleport/gen/proto/go/teleport/lib/teleterm/v1"
api "github.com/gravitational/teleport/gen/proto/go/teleport/lib/teleterm/vnet/v1"
vnetv1 "github.com/gravitational/teleport/gen/proto/go/teleport/lib/vnet/v1"
"github.com/gravitational/teleport/lib/client"
"github.com/gravitational/teleport/lib/teleterm/api/uri"
"github.com/gravitational/teleport/lib/teleterm/clusteridcache"
Expand Down Expand Up @@ -73,7 +72,8 @@ func New(cfg Config) (*Service, error) {
}

return &Service{
cfg: cfg,
cfg: cfg,
clusterConfigCache: vnet.NewClusterConfigCache(cfg.Clock),
}, nil
}

Expand Down Expand Up @@ -125,7 +125,7 @@ func (s *Service) Start(ctx context.Context, req *api.StartRequest) (*api.StartR
return nil, trace.AlreadyExists("VNet is already running")
}

appProvider := &appProvider{
clientApplication := &clientApplication{
daemonService: s.cfg.DaemonService,
insecureSkipVerify: s.cfg.InsecureSkipVerify,
usageReporter: &disabledTelemetryUsageReporter{},
Expand Down Expand Up @@ -156,13 +156,11 @@ func (s *Service) Start(ctx context.Context, req *api.StartRequest) (*api.StartR
usageReporter.Stop()
}
}()
appProvider.usageReporter = usageReporter
clientApplication.usageReporter = usageReporter
}

s.clusterConfigCache = vnet.NewClusterConfigCache(s.cfg.Clock)
processManager, err := vnet.RunUserProcess(ctx, &vnet.UserProcessConfig{
AppProvider: appProvider,
ClusterConfigCache: s.clusterConfigCache,
ClientApplication: clientApplication,
})
if err != nil {
return nil, trace.Wrap(err)
Expand Down Expand Up @@ -196,7 +194,7 @@ func (s *Service) Start(ctx context.Context, req *api.StartRequest) (*api.StartR
}()

s.processManager = processManager
s.usageReporter = appProvider.usageReporter
s.usageReporter = clientApplication.usageReporter
s.status = statusRunning
return &api.StartResponse{}, nil
}
Expand Down Expand Up @@ -355,37 +353,39 @@ func (s *Service) reportUnexpectedShutdown(ctx context.Context, shutdownErr erro
return trace.Wrap(err, "sending shutdown report")
}

type appProvider struct {
type clientApplication struct {
daemonService *daemon.Service
usageReporter usageReporter
insecureSkipVerify bool
}

func (p *appProvider) ListProfiles() ([]string, error) {
func (p *clientApplication) ListProfiles() ([]string, error) {
profiles, err := p.daemonService.ListProfileNames()
return profiles, trace.Wrap(err)
}

func (p *appProvider) GetCachedClient(ctx context.Context, profileName, leafClusterName string) (vnet.ClusterClient, error) {
func (p *clientApplication) GetCachedClient(ctx context.Context, profileName, leafClusterName string) (vnet.ClusterClient, error) {
return p.getCachedClient(ctx, profileName, leafClusterName)
}

func (p *appProvider) getCachedClient(ctx context.Context, profileName, leafClusterName string) (*client.ClusterClient, error) {
func (p *clientApplication) getCachedClient(ctx context.Context, profileName, leafClusterName string) (*client.ClusterClient, error) {
uri := uri.NewClusterURI(profileName).AppendLeafCluster(leafClusterName)
client, err := p.daemonService.GetCachedClient(ctx, uri)
return client, trace.Wrap(err)
}

func (p *appProvider) ReissueAppCert(ctx context.Context, profileName, leafClusterName string, routeToApp proto.RouteToApp) (tls.Certificate, error) {
clusterURI := uri.NewClusterURI(profileName).AppendLeafCluster(leafClusterName)
appURI := clusterURI.AppendApp(routeToApp.Name)
func (p *clientApplication) ReissueAppCert(ctx context.Context, appInfo *vnetv1.AppInfo, targetPort uint16) (tls.Certificate, error) {
appKey := appInfo.GetAppKey()
clusterURI := uri.NewClusterURI(appKey.GetProfile()).AppendLeafCluster(appKey.GetLeafCluster())
appURI := clusterURI.AppendApp(appKey.GetName())

routeToApp := vnet.RouteToApp(appInfo, targetPort)
apiteletermRouteToApp := apiteleterm.RouteToApp{
Name: routeToApp.Name,
PublicAddr: routeToApp.PublicAddr,
ClusterName: routeToApp.ClusterName,
Uri: routeToApp.URI,
TargetPort: routeToApp.TargetPort,
TargetPort: uint32(routeToApp.TargetPort),
}

reloginReq := &apiteleterm.ReloginRequest{
Expand All @@ -411,7 +411,7 @@ func (p *appProvider) ReissueAppCert(ctx context.Context, profileName, leafClust
return trace.Wrap(err)
}

cert, err = cluster.ReissueAppCert(ctx, client, routeToApp)
cert, err = cluster.ReissueAppCert(ctx, client, *routeToApp)
return trace.Wrap(err)
}

Expand Down Expand Up @@ -441,35 +441,35 @@ func (p *appProvider) ReissueAppCert(ctx context.Context, profileName, leafClust
}

// GetDialOptions returns ALPN dial options for the profile.
func (p *appProvider) GetDialOptions(ctx context.Context, profileName string) (*vnet.DialOptions, error) {
func (p *clientApplication) GetDialOptions(ctx context.Context, profileName string) (*vnetv1.DialOptions, error) {
cluster, tc, err := p.daemonService.ResolveClusterURI(uri.NewClusterURI(profileName))
if err != nil {
return nil, trace.Wrap(err, "resolving cluster by URI")
}

dialOpts := &vnet.DialOptions{
dialOpts := &vnetv1.DialOptions{
WebProxyAddr: cluster.GetProxyHost(),
ALPNConnUpgradeRequired: tc.TLSRoutingConnUpgradeRequired,
AlpnConnUpgradeRequired: tc.TLSRoutingConnUpgradeRequired,
InsecureSkipVerify: p.insecureSkipVerify,
}
if dialOpts.ALPNConnUpgradeRequired {
dialOpts.RootClusterCACertPool, err = tc.RootClusterCACertPool(ctx)
if dialOpts.AlpnConnUpgradeRequired {
dialOpts.RootClusterCaCertPool, err = tc.RootClusterCACertPoolPEM(ctx)
if err != nil {
return nil, trace.Wrap(err, "loading root cluster CA cert pool")
}
}
return dialOpts, nil
}

// OnNewConnection submits a usage event once per appProvider lifetime.
// OnNewConnection submits a usage event once per clientApplication lifetime.
// That is, if a user makes multiple connections to a single app, OnNewConnection submits a single
// event. This is to mimic how Connect submits events for its app gateways. This lets us compare
// popularity of VNet and app gateways.
func (p *appProvider) OnNewConnection(ctx context.Context, profileName, leafClusterName string, routeToApp proto.RouteToApp) error {
func (p *clientApplication) OnNewConnection(ctx context.Context, appKey *vnetv1.AppKey) error {
// Enqueue the event from a separate goroutine since we don't care about errors anyway and we also
// don't want to slow down VNet connections.
go func() {
uri := uri.NewClusterURI(profileName).AppendLeafCluster(leafClusterName).AppendApp(routeToApp.Name)
uri := uri.NewClusterURI(appKey.GetProfile()).AppendLeafCluster(appKey.GetLeafCluster()).AppendApp(appKey.GetName())

// Not passing ctx to ReportApp since ctx is tied to the lifetime of the connection.
// If it's a short-lived connection, inheriting its context would interrupt reporting.
Expand All @@ -484,13 +484,17 @@ func (p *appProvider) OnNewConnection(ctx context.Context, profileName, leafClus

// 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.
func (p *appProvider) OnInvalidLocalPort(ctx context.Context, profileName, leafClusterName string, routeToApp proto.RouteToApp, tcpPorts types.PortRanges) {
func (p *clientApplication) OnInvalidLocalPort(ctx context.Context, appInfo *vnetv1.AppInfo, targetPort uint16) {
// If something is wrong with the Electron app to the point that it stopped accepting RPCs, return
// quickly rather than being blocked on sending a notification.
ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
defer cancel()

appURI := uri.NewClusterURI(profileName).AppendLeafCluster(leafClusterName).AppendApp(routeToApp.Name)
appKey := appInfo.GetAppKey()
appURI := uri.NewClusterURI(appKey.GetProfile()).
AppendLeafCluster(appKey.GetLeafCluster()).
AppendApp(appKey.GetName())
routeToApp := vnet.RouteToApp(appInfo, targetPort)
apiteletermRouteToApp := apiteleterm.RouteToApp{
Name: routeToApp.Name,
PublicAddr: routeToApp.PublicAddr,
Expand All @@ -502,6 +506,7 @@ func (p *appProvider) OnInvalidLocalPort(ctx context.Context, profileName, leafC
invalidLocalPort := &apiteleterm.InvalidLocalPort{}
// Send ports only if there's less than 10 ranges. A bigger number would be difficult to show in
// the UI.
tcpPorts := appInfo.GetApp().GetTCPPorts()
if len(tcpPorts) <= 10 {
apiTCPPorts := make([]*apiteleterm.PortRange, 0, len(tcpPorts))
for _, portRange := range tcpPorts {
Expand All @@ -523,7 +528,10 @@ func (p *appProvider) OnInvalidLocalPort(ctx context.Context, profileName, leafC
})
if err != nil {
log.ErrorContext(ctx, "Could not notify the Electron app about invalid local port",
"notify_error", err, "profile_name", profileName, "leaf_cluster_name", leafClusterName, "route_to_app", routeToApp)
"notify_error", err,
"profile_name", appKey.GetProfile(),
"leaf_cluster_name", appKey.GetLeafCluster(),
"route_to_app", routeToApp)
}
}

Expand Down
Loading

0 comments on commit 0fc5882

Please sign in to comment.