mirror of
https://github.com/gravitational/teleport.git
synced 2026-09-21 05:55:42 +08:00
207 lines
8.0 KiB
Go
207 lines
8.0 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 proxy
|
|
|
|
import (
|
|
"context"
|
|
"net"
|
|
"slices"
|
|
|
|
"github.com/gravitational/trace"
|
|
|
|
scopesv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/v1"
|
|
"github.com/gravitational/teleport/api/types"
|
|
"github.com/gravitational/teleport/lib/agentless"
|
|
"github.com/gravitational/teleport/lib/services"
|
|
"github.com/gravitational/teleport/lib/services/readonly"
|
|
"github.com/gravitational/teleport/lib/srv/transport/transportv1"
|
|
"github.com/gravitational/teleport/lib/sshagent"
|
|
"github.com/gravitational/teleport/lib/utils"
|
|
)
|
|
|
|
type RelayAccessPoint interface {
|
|
GetClusterNetworkingConfig(ctx context.Context) (types.ClusterNetworkingConfig, error)
|
|
GetSessionRecordingConfig(ctx context.Context) (types.SessionRecordingConfig, error)
|
|
}
|
|
|
|
type NodeWatcher = *services.GenericWatcher[types.Server, readonly.Server]
|
|
|
|
func getServerForRelay(ctx context.Context, scopePin *scopesv1.Pin, host, port string, accessPoint RelayAccessPoint, nodeWatcher NodeWatcher) (types.Server, error) {
|
|
return getServer(ctx, scopePin, host, port, &relayCluster{
|
|
accessPoint: accessPoint,
|
|
nodeWatcher: nodeWatcher,
|
|
})
|
|
}
|
|
|
|
type relayCluster struct {
|
|
accessPoint RelayAccessPoint
|
|
nodeWatcher NodeWatcher
|
|
}
|
|
|
|
var _ cluster = (*relayCluster)(nil)
|
|
|
|
// GetNodes implements [cluster].
|
|
func (s *relayCluster) GetNodes(ctx context.Context, fn func(n readonly.Server) bool) ([]types.Server, error) {
|
|
return s.nodeWatcher.CurrentResourcesWithFilter(ctx, fn)
|
|
}
|
|
|
|
// GetClusterNetworkingConfig implements [cluster].
|
|
func (s *relayCluster) GetClusterNetworkingConfig(ctx context.Context) (types.ClusterNetworkingConfig, error) {
|
|
return s.accessPoint.GetClusterNetworkingConfig(ctx)
|
|
|
|
}
|
|
|
|
// GetGitServers implements [cluster].
|
|
func (s *relayCluster) GetGitServers(context.Context, func(readonly.Server) bool) ([]types.Server, error) {
|
|
return nil, trace.NotImplemented("connectivity to git servers through the Relay service is not supported")
|
|
}
|
|
|
|
type RelayTunnelDialFunc = func(ctx context.Context, hostID string, tunnelType types.TunnelType, src, dst net.Addr) (net.Conn, error)
|
|
type RelayPeerDialFunc = func(ctx context.Context, hostID string, tunnelType types.TunnelType, relayIDs []string, src, dst net.Addr) (net.Conn, error)
|
|
|
|
type RelayRouterConfig struct {
|
|
// ClusterName is the name of the cluster that the relay belongs to. Routing
|
|
// to hosts in other clusters is not supported.
|
|
ClusterName string
|
|
|
|
// GroupName is the name of the relay group that the relay belongs to.
|
|
// Dialing hosts for which there's no tunnel available locally and that is
|
|
// connected to a different relay group is not supported.
|
|
GroupName string
|
|
|
|
// LocalDial should dial the given target if it's available as a reverse
|
|
// tunnel attached to the local instance.
|
|
LocalDial RelayTunnelDialFunc
|
|
|
|
// PeerDial should dial the given target through a known list of relay IDs.
|
|
// It will only be called for targets that are advertising the same relay
|
|
// group, but the function should check that any given relay in the list
|
|
// belongs to the correct group before attempting to use it.
|
|
PeerDial RelayPeerDialFunc
|
|
|
|
AccessPoint RelayAccessPoint
|
|
NodeWatcher NodeWatcher
|
|
}
|
|
|
|
// NewRelayRouter creates a [transportv1.Dialer] appropriate for use by a relay,
|
|
// with the given parameters. The dialer will not open connections to auth
|
|
// services, agentless SSH servers, or desktops, and it will only dial
|
|
// (agentful) SSH servers if they have an open reverse tunnel to this relay or
|
|
// to a relay in the same group.
|
|
func NewRelayRouter(cfg RelayRouterConfig) (transportv1.Dialer, error) {
|
|
if cfg.ClusterName == "" {
|
|
return nil, trace.BadParameter("missing ClusterName")
|
|
}
|
|
if cfg.GroupName == "" {
|
|
return nil, trace.BadParameter("missing GroupName")
|
|
}
|
|
if cfg.LocalDial == nil {
|
|
return nil, trace.BadParameter("missing LocalDial")
|
|
}
|
|
if cfg.PeerDial == nil {
|
|
return nil, trace.BadParameter("missing PeerDial")
|
|
}
|
|
if cfg.AccessPoint == nil {
|
|
return nil, trace.BadParameter("missing AccessPoint")
|
|
}
|
|
if cfg.NodeWatcher == nil {
|
|
return nil, trace.BadParameter("missing NodeWatcher")
|
|
}
|
|
return &relayRouter{
|
|
clusterName: cfg.ClusterName,
|
|
groupName: cfg.GroupName,
|
|
localDial: cfg.LocalDial,
|
|
peerDial: cfg.PeerDial,
|
|
accessPoint: cfg.AccessPoint,
|
|
nodeWatcher: cfg.NodeWatcher,
|
|
}, nil
|
|
}
|
|
|
|
type relayRouter struct {
|
|
clusterName string
|
|
groupName string
|
|
localDial RelayTunnelDialFunc
|
|
peerDial RelayPeerDialFunc
|
|
accessPoint RelayAccessPoint
|
|
nodeWatcher NodeWatcher
|
|
}
|
|
|
|
var _ transportv1.Dialer = (*relayRouter)(nil)
|
|
|
|
// DialHost implements [transportv1.Dialer].
|
|
func (r *relayRouter) DialHost(ctx context.Context, scopePin *scopesv1.Pin, clientSrcAddr net.Addr, clientDstAddr net.Addr, host string, port string, cluster string, _ func(types.RemoteCluster) error, _ sshagent.ClientGetter, _ agentless.SignerCreator) (net.Conn, error) {
|
|
if cluster != r.clusterName {
|
|
return nil, trace.NotImplemented("dialing nodes for a different cluster through the Relay service is not supported")
|
|
}
|
|
|
|
src, err := r.accessPoint.GetSessionRecordingConfig(ctx)
|
|
if err != nil {
|
|
// deliberately not wrapping the error to not surface this as a NotFound
|
|
// or some other meaningful error
|
|
return nil, trace.Errorf("unable to determine recording mode: %s", err.Error())
|
|
}
|
|
if services.IsRecordAtProxy(src.GetMode()) {
|
|
return nil, trace.NotImplemented("connectivity to SSH servers through the Relay service is not supported in Proxy recording mode")
|
|
}
|
|
|
|
server, err := getServerForRelay(ctx, scopePin, host, port, r.accessPoint, r.nodeWatcher)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
if server == nil {
|
|
return nil, trace.NotImplemented("direct dialing to nodes through the Relay service is not supported")
|
|
}
|
|
|
|
if server.IsOpenSSHNode() {
|
|
return nil, trace.NotImplemented("connectivity to agentless servers through the Relay service is not supported")
|
|
}
|
|
|
|
localConn, err := r.localDial(ctx, server.GetName()+"."+r.clusterName, types.NodeTunnel, clientSrcAddr, clientDstAddr)
|
|
if err == nil {
|
|
return utils.NewConnWithAddr(localConn, clientDstAddr, clientSrcAddr), nil
|
|
}
|
|
if !trace.IsNotFound(err) {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
if server.GetRelayGroup() == "" {
|
|
return nil, trace.NotFound("dial target doesn't appear to be available through any Relay group")
|
|
}
|
|
if server.GetRelayGroup() != r.groupName {
|
|
return nil, trace.NotFound("dial target doesn't appear to be connected through this Relay group")
|
|
}
|
|
|
|
// peerDial might still fail to find a connection based on the advertised
|
|
// relay IDs, the filtering done above is just to make the logic explicit
|
|
peerConn, err := r.peerDial(ctx, server.GetName()+"."+r.clusterName, types.NodeTunnel, slices.Clone(server.GetRelayIDs()), clientSrcAddr, clientDstAddr)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
return utils.NewConnWithAddr(peerConn, clientDstAddr, clientSrcAddr), nil
|
|
}
|
|
|
|
// DialSite implements [transportv1.Dialer].
|
|
func (r *relayRouter) DialSite(context.Context, string, net.Addr, net.Addr) (net.Conn, error) {
|
|
return nil, trace.NotImplemented("connectivity to Auth services through the Relay service is not supported")
|
|
}
|
|
|
|
// DialWindowsDesktop implements [transportv1.Dialer].
|
|
func (r *relayRouter) DialWindowsDesktop(context.Context, net.Addr, net.Addr, string, string, func(types.RemoteCluster) error) (net.Conn, error) {
|
|
return nil, trace.NotImplemented("connectivity to Windows desktops through the Relay service is not supported")
|
|
}
|