-
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
Pass port from VNet to local proxy #49453
Conversation
Multi-port support in VNet (#49453) backported to branch/v17.
lib/vnet/app_resolver.go
Outdated
@@ -109,7 +111,7 @@ type TCPAppResolver struct { | |||
func NewTCPAppResolver(appProvider AppProvider, opts ...tcpAppResolverOption) (*TCPAppResolver, error) { | |||
r := &TCPAppResolver{ | |||
appProvider: appProvider, | |||
slog: slog.With(teleport.ComponentKey, "VNet.AppResolver"), | |||
log: logutils.NewPackageLogger(teleport.ComponentKey, "VNet.AppResolver"), |
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.
Isn't NewPackageLogger
meant to be used for package-level vars?
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 forgot that this package even already defines a package-level log
, I'm going to use it here then.
lib/vnet/app_resolver.go
Outdated
return nil, ErrNoTCPHandler | ||
} | ||
if len(resp.Resources) == 0 { | ||
// Didn't find any matching app, forward the request upstream. | ||
return nil, ErrNoTCPHandler | ||
} | ||
app := resp.Resources[0].GetApp() | ||
appHandler, err := r.newTCPAppHandler(ctx, profileName, leafClusterName, app) | ||
appHandler, err := r.newTCPAppHandler(ctx, log, profileName, leafClusterName, app) |
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.
Hmmm - I don't love passing log
in here because newTCPAppHandler
also has access to r.log
and it gets confusing to try to understand where you should be passing a logger around and where you should be using the logger on the method receiver.
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.
Yeah, I know. But this log
already has all the extra fields specified and they're useful both in the context of resolveTCPHandlerForCluster
, as well as within a TCP app handler itself. I didn't want to repeat them twice, but I suppose doing so would at least reduce the confusion around which logger to use. So I guess that would be the way forward?
lib/vnet/vnet_test.go
Outdated
const defaultPort = 123 | ||
if port == 0 { | ||
port = defaultPort | ||
} |
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.
const defaultPort = 123 | |
if port == 0 { | |
port = defaultPort | |
} | |
port = cmp.Or(port, 123) |
lib/vnet/vnet_test.go
Outdated
@@ -235,8 +241,14 @@ func (n noUpstreamNameservers) UpstreamNameservers(ctx context.Context) ([]strin | |||
return nil, nil | |||
} | |||
|
|||
type appSpec struct { | |||
// publicAddr is used bothas the name of the app and its public address in the final spec. |
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.
// publicAddr is used bothas the name of the app and its public address in the final spec. | |
// publicAddr is used both as the name of the app and its public address in the final spec. |
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 tried this out and it works great! This is sweet
app types.Application | ||
portToLocalProxy map[uint16]*alpnproxy.LocalProxy | ||
// mu guards access to portToLocalProxy. | ||
mu sync.Mutex |
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.
Consider grouping mu
with the fields it protects.
app types.Application | |
portToLocalProxy map[uint16]*alpnproxy.LocalProxy | |
// mu guards access to portToLocalProxy. | |
mu sync.Mutex | |
app types.Application | |
// mu guards access to the fields that follow | |
mu sync.Mutex | |
portToLocalProxy map[uint16]*alpnproxy.LocalProxy |
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.
Oops, forgot to address this. But I think spelling it out explicitly in the comment might be slightly better – at least you can see which fields it protects when you do a lookup on the field in your editor.
Use libutils.NewPackageLogger, call it log instead of slog which makes it harder to use the imported default slog logger instead of the one from a struct. Move creation of logger within TCPAppResolver.resolveTCPHandlerForCluster
daa9898
to
f880b58
Compare
@ravicious See the table below for backport results.
|
* Prepare app specs in tests for specifying TCP ports * Refactor logging in lib/vnet/app_resolver.go Use libutils.NewPackageLogger, call it log instead of slog which makes it harder to use the imported default slog logger instead of the one from a struct. Move creation of logger within TCPAppResolver.resolveTCPHandlerForCluster * Pass port from VNet to local proxy * Don't create another package logger * Don't pass logger to newTCPAppHandler * Fix typo in comment * Explicitly pass port to dialHost * Convert multi-line definitions of simple appSpecs to single-line * Add TODO comment about validating local port * Empty commit to trigger CI
Depends on #49047 and #49349. This PR implements the section of the RFD called Passing the port number from VNet to the client.
The way VNet used to work was that when a DNS query came in for an FQDN that matched the public address of an app in the cluster, VNet would create a virtual IP for it and a local proxy (ala
tsh proxy app
). Then the TCP connection would be routed through that proxy to the URI specified in the app spec.Adding multi-port support means that if we have an app spec such as this:
then a user should be able to start VNet and do
curl http://multi-port-example.cluster.example.com:8765
on their computer. This connection should be then routed tolocalhost:8765
on the machine that's running the app service.The backend part was implemented in #49047. This PR makes it so that when a TCP connection comes in, VNet reads the port and only then creates a local proxy with a proper
TargetPort
set inRouteToApp
.Implementation details
lib/vnet/vnet.go
hasNetworkStack.handleTCP
which gets called on a new TCP connection. At that point, VNet grabs atcpAppHandler
, which is still created when a DNS query comes in – a handler is still associated with an FQDN of an app.However, the underlying proxy is not created at the time when
tcpAppHandler
is initialized. Instead, fromNetworkStack.handleTCP
we pass the local port totcpAppHandler.HandleTCPConnector
which then gets or initializes a local proxy associated with that local port. The local proxy hasRouteToApp.TargetPort
set to the local port.The tests are completely removed from how Teleport does routing or cert generation. So to verify that multi-port support works correctly within VNet, we verify if
lib/vnet.AppProvider.ReissueAppCert
is called with correct port inRouteToApp
.How to test this
First compile teleport and tsh from this branch. Then add a TCP app like in the snippet above. I use
mendhak/http-https-echo
Docker image for the HTTP echo.Then start VNet with
tsh vnet -d
and connect to either of the ports using an appropriate client. If you're using a local cluster, make sure<app-name>.<cluster-address>
is not in/etc/hosts
.