Skip to content

Commit

Permalink
[v17][vnet] remote OS config provider (#51897)
Browse files Browse the repository at this point in the history
Backport #51629 to branch/v17
  • Loading branch information
nklaassen authored Feb 6, 2025
1 parent 5e6293a commit 62c2947
Show file tree
Hide file tree
Showing 16 changed files with 743 additions and 206 deletions.
425 changes: 302 additions & 123 deletions gen/proto/go/teleport/lib/vnet/v1/client_application_service.pb.go

Large diffs are not rendered by default.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion lib/vnet/admin_process_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ loop:
func newWindowsNetworkStackConfig(tun tunDevice, clt *clientApplicationServiceClient) (*networkStackConfig, error) {
appProvider := newRemoteAppProvider(clt)
appResolver := newTCPAppResolver(appProvider, clockwork.NewRealClock())
ipv6Prefix, err := NewIPv6Prefix()
ipv6Prefix, err := newIPv6Prefix()
if err != nil {
return nil, trace.Wrap(err, "creating new IPv6 prefix")
}
Expand Down
25 changes: 17 additions & 8 deletions lib/vnet/client_application_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ type clientApplicationService struct {
// [vnetv1.ClientApplicationServiceServer]
vnetv1.UnsafeClientApplicationServiceServer

appProvider appProvider
localAppProvider *localAppProvider

// mu protects appSignerCache
mu sync.Mutex
Expand All @@ -50,10 +50,10 @@ type clientApplicationService struct {
appSignerCache map[appKey]crypto.Signer
}

func newClientApplicationService(appProvider appProvider) *clientApplicationService {
func newClientApplicationService(localAppProvider *localAppProvider) *clientApplicationService {
return &clientApplicationService{
appProvider: appProvider,
appSignerCache: make(map[appKey]crypto.Signer),
localAppProvider: localAppProvider,
appSignerCache: make(map[appKey]crypto.Signer),
}
}

Expand All @@ -77,7 +77,7 @@ func (s *clientApplicationService) AuthenticateProcess(ctx context.Context, req

// ResolveAppInfo implements [vnetv1.ClientApplicationServiceServer.ResolveAppInfo].
func (s *clientApplicationService) ResolveAppInfo(ctx context.Context, req *vnetv1.ResolveAppInfoRequest) (*vnetv1.ResolveAppInfoResponse, error) {
appInfo, err := s.appProvider.ResolveAppInfo(ctx, req.GetFqdn())
appInfo, err := s.localAppProvider.ResolveAppInfo(ctx, req.GetFqdn())
if err != nil {
return nil, trace.Wrap(err, "resolving app info")
}
Expand All @@ -93,7 +93,7 @@ func (s *clientApplicationService) ReissueAppCert(ctx context.Context, req *vnet
if req.AppInfo == nil {
return nil, trace.BadParameter("missing AppInfo")
}
cert, err := s.appProvider.ReissueAppCert(ctx, req.GetAppInfo(), uint16(req.GetTargetPort()))
cert, err := s.localAppProvider.ReissueAppCert(ctx, req.GetAppInfo(), uint16(req.GetTargetPort()))
if err != nil {
return nil, trace.Wrap(err, "reissuing app certificate")
}
Expand Down Expand Up @@ -153,7 +153,7 @@ func (s *clientApplicationService) getSignerForApp(appKey *vnetv1.AppKey, target
// OnNewConnection gets called whenever a new connection is about to be
// established through VNet for observability.
func (s *clientApplicationService) OnNewConnection(ctx context.Context, req *vnetv1.OnNewConnectionRequest) (*vnetv1.OnNewConnectionResponse, error) {
if err := s.appProvider.OnNewConnection(ctx, req.GetAppKey()); err != nil {
if err := s.localAppProvider.OnNewConnection(ctx, req.GetAppKey()); err != nil {
return nil, trace.Wrap(err)
}
return &vnetv1.OnNewConnectionResponse{}, nil
Expand All @@ -163,7 +163,7 @@ func (s *clientApplicationService) OnNewConnection(ctx context.Context, req *vne
// to a multi-port TCP app because the provided port does not match any of the
// TCP ports in the app spec.
func (s *clientApplicationService) OnInvalidLocalPort(ctx context.Context, req *vnetv1.OnInvalidLocalPortRequest) (*vnetv1.OnInvalidLocalPortResponse, error) {
s.appProvider.OnInvalidLocalPort(ctx, req.GetAppInfo(), uint16(req.GetTargetPort()))
s.localAppProvider.OnInvalidLocalPort(ctx, req.GetAppInfo(), uint16(req.GetTargetPort()))
return &vnetv1.OnInvalidLocalPortResponse{}, nil
}

Expand All @@ -182,3 +182,12 @@ func newAppKey(protoAppKey *vnetv1.AppKey, port uint16) appKey {
port: port,
}
}

// GetTargetOSConfiguration returns the configuration values that should be
// configured in the OS, including DNS zones that should be handled by the VNet
// DNS nameserver and the IPv4 CIDR ranges that should be routed to the VNet TUN
// interface.
func (s *clientApplicationService) GetTargetOSConfiguration(ctx context.Context, _ *vnetv1.GetTargetOSConfigurationRequest) (*vnetv1.GetTargetOSConfigurationResponse, error) {
resp, err := s.localAppProvider.getTargetOSConfiguration(ctx)
return resp, trace.Wrap(err, "getting target OS configuration")
}
12 changes: 12 additions & 0 deletions lib/vnet/client_application_service_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,3 +137,15 @@ func (c *clientApplicationServiceClient) OnInvalidLocalPort(ctx context.Context,
}
return nil
}

// GetTargetOSConfiguration returns the configuration values that should be
// configured in the OS, including DNS zones that should be handled by the VNet
// DNS nameserver and the IPv4 CIDR ranges that should be routed to the VNet TUN
// interface.
func (c *clientApplicationServiceClient) GetTargetOSConfiguration(ctx context.Context) (*vnetv1.TargetOSConfiguration, error) {
resp, err := c.clt.GetTargetOSConfiguration(ctx, &vnetv1.GetTargetOSConfigurationRequest{})
if err != nil {
return nil, trace.Wrap(err, "calling GetTargetOSConfiguration rpc")
}
return resp.GetTargetOsConfiguration(), nil
}
15 changes: 3 additions & 12 deletions lib/vnet/clusterconfigcache.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,6 @@ import (
)

type ClusterConfig struct {
// ClusterName is the name of the cluster as reported by Ping.
ClusterName string
// ProxyPublicAddr is the public address of the proxy as reported by Ping, with any ports removed, this is
// just the hostname. This is often but not always identical to the ClusterName. For root clusters this
// will be the same as the profile name (the profile is named after the proxy public addr).
ProxyPublicAddr string
// DNSZones is the list of DNS zones that are valid for this cluster, this includes ProxyPublicAddr *and*
// any configured custom DNS zones for the cluster.
DNSZones []string
Expand Down Expand Up @@ -112,7 +106,6 @@ func (c *ClusterConfigCache) getClusterConfigUncached(ctx context.Context, clust
return nil, trace.Wrap(err)
}

clusterName := pingResp.ClusterName
proxyPublicAddr := pingResp.ProxyPublicAddr
if strings.Contains(proxyPublicAddr, ":") {
proxyPublicAddr, _, err = net.SplitHostPort(pingResp.ProxyPublicAddr)
Expand All @@ -137,10 +130,8 @@ func (c *ClusterConfigCache) getClusterConfigUncached(ctx context.Context, clust
}

return &ClusterConfig{
ClusterName: clusterName,
ProxyPublicAddr: proxyPublicAddr,
DNSZones: dnsZones,
IPv4CIDRRange: ipv4CIDRRange,
Expires: c.clock.Now().Add(5 * time.Minute),
DNSZones: dnsZones,
IPv4CIDRRange: ipv4CIDRRange,
Expires: c.clock.Now().Add(5 * time.Minute),
}, nil
}
4 changes: 2 additions & 2 deletions lib/vnet/ipbits.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ import (
"gvisor.dev/gvisor/pkg/tcpip"
)

// NewIPv6Prefix returns a Unique Local IPv6 Unicast Address which will be used as a 64-bit prefix for all v6
// newIPv6Prefix returns a Unique Local IPv6 Unicast Address which will be used as a 64-bit prefix for all v6
// IP addresses in the VNet.
func NewIPv6Prefix() (tcpip.Address, error) {
func newIPv6Prefix() (tcpip.Address, error) {
// | 8 bits | 40 bits | 16 bits | 64 bits |
// +------------+------------+-----------+----------------------------+
// | ULA Prefix | Global ID | Subnet ID | Interface ID |
Expand Down
70 changes: 70 additions & 0 deletions lib/vnet/local_app_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -243,3 +243,73 @@ func (p *localAppProvider) resolveAppInfoForCluster(
}
return appInfo, nil
}

// getTargetOSConfiguration returns the configuration values that should be
// configured in the OS, including DNS zones that should be handled by the VNet
// DNS nameserver and the IPv4 CIDR ranges that should be routed to the VNet TUN
// interface. This is not all of the OS configuration values, only the ones that
// must be communicated from the client application to the admin process.
func (p *localAppProvider) getTargetOSConfiguration(ctx context.Context) (*vnetv1.GetTargetOSConfigurationResponse, error) {
profiles, err := p.ClientApplication.ListProfiles()
if err != nil {
return nil, trace.Wrap(err, "listing profiles")
}
var targetOSConfig vnetv1.TargetOSConfiguration
for _, profileName := range profiles {
profileTargetConfig := p.targetOSConfigurationForProfile(ctx, profileName)
targetOSConfig.DnsZones = append(targetOSConfig.DnsZones, profileTargetConfig.DnsZones...)
targetOSConfig.Ipv4CidrRanges = append(targetOSConfig.Ipv4CidrRanges, profileTargetConfig.Ipv4CidrRanges...)
}
return &vnetv1.GetTargetOSConfigurationResponse{
TargetOsConfiguration: &targetOSConfig,
}, nil
}

// targetOSConfigurationForProfile does not return errors, it is better to
// configure VNet for any working profiles and log errors for failures.
func (p *localAppProvider) targetOSConfigurationForProfile(ctx context.Context, profileName string) *vnetv1.TargetOSConfiguration {
targetOSConfig := &vnetv1.TargetOSConfiguration{}
rootClusterClient, err := p.GetCachedClient(ctx, profileName, "" /*leafClusterName*/)
if err != nil {
log.WarnContext(ctx,
"Failed to get root cluster client from cache, profile may be expired, not configuring VNet for this cluster",
"profile", profileName, "error", err)
return targetOSConfig
}
rootClusterConfig, err := p.clusterConfigCache.GetClusterConfig(ctx, rootClusterClient)
if err != nil {
log.WarnContext(ctx,
"Failed to load VNet configuration, profile may be expired, not configuring VNet for this cluster",
"profile", profileName, "error", err)
return targetOSConfig
}
targetOSConfig.DnsZones = rootClusterConfig.DNSZones
targetOSConfig.Ipv4CidrRanges = []string{rootClusterConfig.IPv4CIDRRange}

leafClusterNames, err := getLeafClusters(ctx, rootClusterClient)
if err != nil {
log.WarnContext(ctx,
"Failed to list leaf clusters, profile may be expired, not configuring VNet for leaf clusters of this cluster",
"profile", profileName, "error", err)
return targetOSConfig
}
for _, leafClusterName := range leafClusterNames {
leafClusterClient, err := p.GetCachedClient(ctx, profileName, leafClusterName)
if err != nil {
log.WarnContext(ctx,
"Failed to create leaf cluster client, not configuring VNet for this cluster",
"profile", profileName, "leaf_cluster", leafClusterName, "error", err)
return targetOSConfig
}
leafClusterConfig, err := p.clusterConfigCache.GetClusterConfig(ctx, leafClusterClient)
if err != nil {
log.WarnContext(ctx,
"Failed to load VNet configuration, not configuring VNet for this cluster",
"profile", profileName, "leaf_cluster", leafClusterName, "error", err)
return targetOSConfig
}
targetOSConfig.DnsZones = append(targetOSConfig.DnsZones, leafClusterConfig.DNSZones...)
targetOSConfig.Ipv4CidrRanges = append(targetOSConfig.Ipv4CidrRanges, leafClusterConfig.IPv4CIDRRange)
}
return targetOSConfig
}
15 changes: 15 additions & 0 deletions lib/vnet/osconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package vnet

import (
"context"
"net"
"net/netip"
"time"

Expand Down Expand Up @@ -120,3 +121,17 @@ func tunIPv6ForPrefix(ipv6Prefix string) (string, error) {
}
return addr.Next().String(), nil
}

// tunIPv4ForCIDR returns the IPv4 address to use for the TUN interface in
// cidrRange. It always returns the second address in the range.
func tunIPv4ForCIDR(cidrRange string) (string, error) {
_, ipnet, err := net.ParseCIDR(cidrRange)
if err != nil {
return "", trace.Wrap(err, "parsing CIDR %q", cidrRange)
}
// ipnet.IP is the network address, ending in 0s, like 100.64.0.0
// Add 1 to assign the TUN address, like 100.64.0.1
tunAddress := ipnet.IP
tunAddress[len(tunAddress)-1]++
return tunAddress.String(), nil
}
17 changes: 5 additions & 12 deletions lib/vnet/profile_osconfig_provider_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ package vnet

import (
"context"
"net"
"os"
"sync/atomic"
"syscall"
Expand Down Expand Up @@ -55,7 +54,7 @@ func newProfileOSConfigProvider(tunName, ipv6Prefix, dnsAddr, homePath string, d
// This runs as root so we need to be configured with the user's home path.
return nil, trace.BadParameter("homePath must be passed from unprivileged process")
}
tunIP, err := tunIPv6ForPrefix(ipv6Prefix)
tunIPv6, err := tunIPv6ForPrefix(ipv6Prefix)
if err != nil {
return nil, trace.Wrap(err)
}
Expand All @@ -65,7 +64,7 @@ func newProfileOSConfigProvider(tunName, ipv6Prefix, dnsAddr, homePath string, d
clusterConfigCache: NewClusterConfigCache(clockwork.NewRealClock()),
daemonClientCred: daemonClientCred,
tunName: tunName,
tunIPv6: tunIP,
tunIPv6: tunIPv6,
dnsAddr: dnsAddr,
homePath: homePath,
}
Expand Down Expand Up @@ -208,17 +207,11 @@ func (p *profileOSConfigProvider) setTunIPv4FromCIDR(cidrRange string) error {
if p.tunIPv4 != "" {
return nil
}

_, ipnet, err := net.ParseCIDR(cidrRange)
ip, err := tunIPv4ForCIDR(cidrRange)
if err != nil {
return trace.Wrap(err, "parsing CIDR %q", cidrRange)
return trace.Wrap(err, "setting TUN IPv4 address for range %s", cidrRange)
}

// ipnet.IP is the network address, ending in 0s, like 100.64.0.0
// Add 1 to assign the TUN address, like 100.64.0.1
tunAddress := ipnet.IP
tunAddress[len(tunAddress)-1]++
p.tunIPv4 = tunAddress.String()
p.tunIPv4 = ip
return nil
}

Expand Down
Loading

0 comments on commit 62c2947

Please sign in to comment.