mirror of
https://github.com/gravitational/teleport.git
synced 2026-09-01 16:03:55 +08:00
Relay peering client and server (#58854)
* proto: relay peering messages * make grpc * Relay peering server and client * fallible client creation, comments * server comments
This commit is contained in:
committed by
GitHub
parent
e8400418c8
commit
e1102466ae
@@ -0,0 +1,284 @@
|
||||
// Teleport
|
||||
// Copyright (C) 2024 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/>.
|
||||
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.8
|
||||
// protoc (unknown)
|
||||
// source: teleport/relaypeering/v1alpha/dial.proto
|
||||
|
||||
package relaypeeringv1alpha
|
||||
|
||||
import (
|
||||
status "google.golang.org/genproto/googleapis/rpc/status"
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
unsafe "unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
// the message sent by the client at the beginning of a connection
|
||||
type DialRequest struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
// the target agent for the connection attempt; should be "<host id>.<cluster name>"
|
||||
TargetHostId string `protobuf:"bytes,1,opt,name=target_host_id,json=targetHostId,proto3" json:"target_host_id,omitempty"`
|
||||
// the type of the connection as defined by api/types.TunnelType ("node",
|
||||
// "app", "kube"...)
|
||||
ConnectionType string `protobuf:"bytes,2,opt,name=connection_type,json=connectionType,proto3" json:"connection_type,omitempty"`
|
||||
Source *Addr `protobuf:"bytes,3,opt,name=source,proto3" json:"source,omitempty"`
|
||||
Destination *Addr `protobuf:"bytes,4,opt,name=destination,proto3" json:"destination,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *DialRequest) Reset() {
|
||||
*x = DialRequest{}
|
||||
mi := &file_teleport_relaypeering_v1alpha_dial_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *DialRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*DialRequest) ProtoMessage() {}
|
||||
|
||||
func (x *DialRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_teleport_relaypeering_v1alpha_dial_proto_msgTypes[0]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use DialRequest.ProtoReflect.Descriptor instead.
|
||||
func (*DialRequest) Descriptor() ([]byte, []int) {
|
||||
return file_teleport_relaypeering_v1alpha_dial_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *DialRequest) GetTargetHostId() string {
|
||||
if x != nil {
|
||||
return x.TargetHostId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *DialRequest) GetConnectionType() string {
|
||||
if x != nil {
|
||||
return x.ConnectionType
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *DialRequest) GetSource() *Addr {
|
||||
if x != nil {
|
||||
return x.Source
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *DialRequest) GetDestination() *Addr {
|
||||
if x != nil {
|
||||
return x.Destination
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// the message sent by the server as a response to a DialRequest; in case of
|
||||
// success (status is missing or reports "ok") the connection data follows on
|
||||
// the same stream, otherwise the stream ends and any data sent by the client is
|
||||
// to be treated as silently discarded
|
||||
type DialResponse struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Status *status.Status `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *DialResponse) Reset() {
|
||||
*x = DialResponse{}
|
||||
mi := &file_teleport_relaypeering_v1alpha_dial_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *DialResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*DialResponse) ProtoMessage() {}
|
||||
|
||||
func (x *DialResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_teleport_relaypeering_v1alpha_dial_proto_msgTypes[1]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use DialResponse.ProtoReflect.Descriptor instead.
|
||||
func (*DialResponse) Descriptor() ([]byte, []int) {
|
||||
return file_teleport_relaypeering_v1alpha_dial_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *DialResponse) GetStatus() *status.Status {
|
||||
if x != nil {
|
||||
return x.Status
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// a stringy Go net.Addr, usually converted to and from lib/utils.NetAddr
|
||||
type Addr struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
// likely always "tcp"
|
||||
Network string `protobuf:"bytes,1,opt,name=network,proto3" json:"network,omitempty"`
|
||||
// depending on the network, likely "<ip address>:<port>"
|
||||
Addr string `protobuf:"bytes,2,opt,name=addr,proto3" json:"addr,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *Addr) Reset() {
|
||||
*x = Addr{}
|
||||
mi := &file_teleport_relaypeering_v1alpha_dial_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *Addr) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*Addr) ProtoMessage() {}
|
||||
|
||||
func (x *Addr) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_teleport_relaypeering_v1alpha_dial_proto_msgTypes[2]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use Addr.ProtoReflect.Descriptor instead.
|
||||
func (*Addr) Descriptor() ([]byte, []int) {
|
||||
return file_teleport_relaypeering_v1alpha_dial_proto_rawDescGZIP(), []int{2}
|
||||
}
|
||||
|
||||
func (x *Addr) GetNetwork() string {
|
||||
if x != nil {
|
||||
return x.Network
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Addr) GetAddr() string {
|
||||
if x != nil {
|
||||
return x.Addr
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
var File_teleport_relaypeering_v1alpha_dial_proto protoreflect.FileDescriptor
|
||||
|
||||
const file_teleport_relaypeering_v1alpha_dial_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"(teleport/relaypeering/v1alpha/dial.proto\x12\x1dteleport.relaypeering.v1alpha\x1a\x17google/rpc/status.proto\"\xe0\x01\n" +
|
||||
"\vDialRequest\x12$\n" +
|
||||
"\x0etarget_host_id\x18\x01 \x01(\tR\ftargetHostId\x12'\n" +
|
||||
"\x0fconnection_type\x18\x02 \x01(\tR\x0econnectionType\x12;\n" +
|
||||
"\x06source\x18\x03 \x01(\v2#.teleport.relaypeering.v1alpha.AddrR\x06source\x12E\n" +
|
||||
"\vdestination\x18\x04 \x01(\v2#.teleport.relaypeering.v1alpha.AddrR\vdestination\":\n" +
|
||||
"\fDialResponse\x12*\n" +
|
||||
"\x06status\x18\x01 \x01(\v2\x12.google.rpc.StatusR\x06status\"4\n" +
|
||||
"\x04Addr\x12\x18\n" +
|
||||
"\anetwork\x18\x01 \x01(\tR\anetwork\x12\x12\n" +
|
||||
"\x04addr\x18\x02 \x01(\tR\x04addrBbZ`github.com/gravitational/teleport/gen/proto/go/teleport/relaypeering/v1alpha;relaypeeringv1alphab\x06proto3"
|
||||
|
||||
var (
|
||||
file_teleport_relaypeering_v1alpha_dial_proto_rawDescOnce sync.Once
|
||||
file_teleport_relaypeering_v1alpha_dial_proto_rawDescData []byte
|
||||
)
|
||||
|
||||
func file_teleport_relaypeering_v1alpha_dial_proto_rawDescGZIP() []byte {
|
||||
file_teleport_relaypeering_v1alpha_dial_proto_rawDescOnce.Do(func() {
|
||||
file_teleport_relaypeering_v1alpha_dial_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_teleport_relaypeering_v1alpha_dial_proto_rawDesc), len(file_teleport_relaypeering_v1alpha_dial_proto_rawDesc)))
|
||||
})
|
||||
return file_teleport_relaypeering_v1alpha_dial_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_teleport_relaypeering_v1alpha_dial_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
|
||||
var file_teleport_relaypeering_v1alpha_dial_proto_goTypes = []any{
|
||||
(*DialRequest)(nil), // 0: teleport.relaypeering.v1alpha.DialRequest
|
||||
(*DialResponse)(nil), // 1: teleport.relaypeering.v1alpha.DialResponse
|
||||
(*Addr)(nil), // 2: teleport.relaypeering.v1alpha.Addr
|
||||
(*status.Status)(nil), // 3: google.rpc.Status
|
||||
}
|
||||
var file_teleport_relaypeering_v1alpha_dial_proto_depIdxs = []int32{
|
||||
2, // 0: teleport.relaypeering.v1alpha.DialRequest.source:type_name -> teleport.relaypeering.v1alpha.Addr
|
||||
2, // 1: teleport.relaypeering.v1alpha.DialRequest.destination:type_name -> teleport.relaypeering.v1alpha.Addr
|
||||
3, // 2: teleport.relaypeering.v1alpha.DialResponse.status:type_name -> google.rpc.Status
|
||||
3, // [3:3] is the sub-list for method output_type
|
||||
3, // [3:3] is the sub-list for method input_type
|
||||
3, // [3:3] is the sub-list for extension type_name
|
||||
3, // [3:3] is the sub-list for extension extendee
|
||||
0, // [0:3] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_teleport_relaypeering_v1alpha_dial_proto_init() }
|
||||
func file_teleport_relaypeering_v1alpha_dial_proto_init() {
|
||||
if File_teleport_relaypeering_v1alpha_dial_proto != nil {
|
||||
return
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_teleport_relaypeering_v1alpha_dial_proto_rawDesc), len(file_teleport_relaypeering_v1alpha_dial_proto_rawDesc)),
|
||||
NumEnums: 0,
|
||||
NumMessages: 3,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
GoTypes: file_teleport_relaypeering_v1alpha_dial_proto_goTypes,
|
||||
DependencyIndexes: file_teleport_relaypeering_v1alpha_dial_proto_depIdxs,
|
||||
MessageInfos: file_teleport_relaypeering_v1alpha_dial_proto_msgTypes,
|
||||
}.Build()
|
||||
File_teleport_relaypeering_v1alpha_dial_proto = out.File
|
||||
file_teleport_relaypeering_v1alpha_dial_proto_goTypes = nil
|
||||
file_teleport_relaypeering_v1alpha_dial_proto_depIdxs = nil
|
||||
}
|
||||
@@ -3157,6 +3157,14 @@ func applyRelayConfig(fc *FileConfig, cfg *servicecfg.Config) error {
|
||||
}
|
||||
cfg.Relay.PeerListenAddr = peerListenAddr
|
||||
|
||||
if fc.Relay.PeerPublicAddr != "" {
|
||||
_, _, err := net.SplitHostPort(fc.Relay.PeerPublicAddr)
|
||||
if err != nil {
|
||||
return trace.Wrap(err, "parsing relay_service.peer_public_addr")
|
||||
}
|
||||
cfg.Relay.PeerPublicAddr = fc.Relay.PeerPublicAddr
|
||||
}
|
||||
|
||||
if fc.Relay.TunnelListenAddr == "" {
|
||||
return trace.BadParameter("missing relay_service.tunnel_listen_addr")
|
||||
}
|
||||
|
||||
@@ -2957,6 +2957,10 @@ type Relay struct {
|
||||
// format.
|
||||
PeerListenAddr string `yaml:"peer_listen_addr"`
|
||||
|
||||
// PeerPublicAddr, if set, is the public address for the peer listener, in
|
||||
// host:port format.
|
||||
PeerPublicAddr string `yaml:"peer_public_addr"`
|
||||
|
||||
// TunnelListenAddr is the listen address for the tunnel listener, in
|
||||
// addr:port format.
|
||||
TunnelListenAddr string `yaml:"tunnel_listen_addr"`
|
||||
|
||||
@@ -0,0 +1,270 @@
|
||||
// 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 relaypeer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"log/slog"
|
||||
"net"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"github.com/gravitational/trace"
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
presencev1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/presence/v1"
|
||||
"github.com/gravitational/teleport/api/trail"
|
||||
"github.com/gravitational/teleport/api/types"
|
||||
relaypeeringv1alpha "github.com/gravitational/teleport/gen/proto/go/teleport/relaypeering/v1alpha"
|
||||
"github.com/gravitational/teleport/lib/tlsca"
|
||||
"github.com/gravitational/teleport/lib/utils"
|
||||
)
|
||||
|
||||
type ClientAccessPoint interface {
|
||||
GetRelayServer(ctx context.Context, name string) (*presencev1.RelayServer, error)
|
||||
}
|
||||
|
||||
// ClientConfig contains parameters for [NewClient].
|
||||
type ClientConfig struct {
|
||||
// HostID is the host ID of the local machine, to avoid peering with
|
||||
// ourselves.
|
||||
HostID string
|
||||
// ClusterName is the name of the Teleport cluster we belong to.
|
||||
ClusterName string
|
||||
// GroupName is the relay group we belong to, to avoid attempting to connect
|
||||
// to relays from a different group.
|
||||
GroupName string
|
||||
// AccessPoint is used to read information about other relays in the same
|
||||
// group.
|
||||
AccessPoint ClientAccessPoint
|
||||
|
||||
Log *slog.Logger
|
||||
|
||||
GetCertificate func() (*tls.Certificate, error)
|
||||
GetPool func() (*x509.CertPool, error)
|
||||
Ciphersuites []uint16
|
||||
}
|
||||
|
||||
// NewClient creates a [Client] with a given configuration.
|
||||
func NewClient(cfg ClientConfig) (*Client, error) {
|
||||
if cfg.HostID == "" {
|
||||
return nil, trace.BadParameter("missing HostID")
|
||||
}
|
||||
if cfg.ClusterName == "" {
|
||||
return nil, trace.BadParameter("missing ClusterName")
|
||||
}
|
||||
if cfg.GroupName == "" {
|
||||
return nil, trace.BadParameter("missing GroupName")
|
||||
}
|
||||
if cfg.AccessPoint == nil {
|
||||
return nil, trace.BadParameter("missing AccessPoint")
|
||||
}
|
||||
if cfg.Log == nil {
|
||||
return nil, trace.BadParameter("missing Log")
|
||||
}
|
||||
if cfg.GetCertificate == nil {
|
||||
return nil, trace.BadParameter("missing GetCertificate")
|
||||
}
|
||||
if cfg.GetPool == nil {
|
||||
return nil, trace.BadParameter("missing GetPool")
|
||||
}
|
||||
|
||||
return &Client{
|
||||
hostID: cfg.HostID,
|
||||
clusterName: cfg.ClusterName,
|
||||
groupName: cfg.GroupName,
|
||||
accessPoint: cfg.AccessPoint,
|
||||
|
||||
log: cfg.Log,
|
||||
|
||||
getCertificate: cfg.GetCertificate,
|
||||
getPool: cfg.GetPool,
|
||||
ciphersuites: cfg.Ciphersuites,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Client is used to open tunnel connections to services that are connected with
|
||||
// relay tunnels to the local relay group, but don't have a tunnel available
|
||||
// locally, and thus require bouncing the connection to a different peer relay
|
||||
// in the same relay group. It implements the client side of the relay peer
|
||||
// dialing protocol, and connects to peer relays through the advertised peer
|
||||
// address in the relay_server heartbeats.
|
||||
type Client struct {
|
||||
hostID string
|
||||
clusterName string
|
||||
groupName string
|
||||
accessPoint ClientAccessPoint
|
||||
|
||||
log *slog.Logger
|
||||
|
||||
getCertificate func() (*tls.Certificate, error)
|
||||
getPool func() (*x509.CertPool, error)
|
||||
ciphersuites []uint16
|
||||
}
|
||||
|
||||
// Dial will try to open a connection to a target host (in "<host id>.<cluster
|
||||
// name>" format) with a given tunnel type, trying to dial through a list of
|
||||
// peer relay IDs in random order. The list is assumed to be owned by Dial and
|
||||
// will be shuffled in place. The returned connection will have the given source
|
||||
// (remote) and destination (local) addresses.
|
||||
func (c *Client) Dial(ctx context.Context, dialTarget string, tunnelType types.TunnelType, relayIDs []string, src, dst net.Addr) (net.Conn, error) {
|
||||
// it's assumed that all peer relays are equivalent
|
||||
for _, relayID := range utils.ShuffleVisit(relayIDs) {
|
||||
if relayID == c.hostID {
|
||||
continue
|
||||
}
|
||||
nc, err := c.dialRelay(ctx, dialTarget, tunnelType, relayID, src, dst)
|
||||
if err == nil {
|
||||
c.log.DebugContext(ctx, "Successfully dialed through peer relay", "relay_id", relayID)
|
||||
return nc, nil
|
||||
}
|
||||
c.log.DebugContext(ctx, "Failed to dial through peer relay", "relay_id", relayID, "error", err, "target", dialTarget)
|
||||
}
|
||||
return nil, trace.ConnectionProblem(nil, "unable to reach dial target through relay peering")
|
||||
}
|
||||
|
||||
// dialRelay tries to open a connection to a target through a specific peer
|
||||
// relay.
|
||||
func (c *Client) dialRelay(ctx context.Context, dialTarget string, tunnelType types.TunnelType, relayID string, src net.Addr, dst net.Addr) (net.Conn, error) {
|
||||
relayServer, err := c.accessPoint.GetRelayServer(ctx, relayID)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
if relayServer.GetSpec().GetRelayGroup() != c.groupName {
|
||||
return nil, trace.BadParameter("peer relay server belongs to different relay group")
|
||||
}
|
||||
|
||||
peerAddr := relayServer.GetSpec().GetPeerAddr()
|
||||
if peerAddr == "" {
|
||||
return nil, trace.BadParameter("no peer addr in peer relay server")
|
||||
}
|
||||
|
||||
cert, err := c.getCertificate()
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
pool, err := c.getPool()
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
nc, err := new(net.Dialer).DialContext(ctx, "tcp", peerAddr)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
serverName, _, err := net.SplitHostPort(peerAddr)
|
||||
if err != nil {
|
||||
serverName = peerAddr
|
||||
}
|
||||
|
||||
tlsConfig := &tls.Config{
|
||||
GetClientCertificate: func(*tls.CertificateRequestInfo) (*tls.Certificate, error) {
|
||||
return cert, nil
|
||||
},
|
||||
|
||||
InsecureSkipVerify: true,
|
||||
VerifyConnection: func(cs tls.ConnectionState) error {
|
||||
if cs.NegotiatedProtocol == "" {
|
||||
return trace.NotImplemented("relay peer protocol not supported")
|
||||
}
|
||||
|
||||
opts := x509.VerifyOptions{
|
||||
DNSName: "",
|
||||
|
||||
Roots: pool,
|
||||
Intermediates: nil,
|
||||
|
||||
KeyUsages: []x509.ExtKeyUsage{
|
||||
x509.ExtKeyUsageServerAuth,
|
||||
},
|
||||
}
|
||||
if len(cs.PeerCertificates) > 1 {
|
||||
opts.Intermediates = x509.NewCertPool()
|
||||
for _, cert := range cs.PeerCertificates[1:] {
|
||||
opts.Intermediates.AddCert(cert)
|
||||
}
|
||||
}
|
||||
if _, err := cs.PeerCertificates[0].Verify(opts); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
id, err := tlsca.FromSubject(cs.PeerCertificates[0].Subject, cs.PeerCertificates[0].NotAfter)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
if !slices.Contains(id.Groups, string(types.RoleRelay)) &&
|
||||
!slices.Contains(id.SystemRoles, string(types.RoleRelay)) {
|
||||
return trace.BadParameter("dialed server is not a relay (roles %+q, system roles %+q)", id.Groups, id.SystemRoles)
|
||||
}
|
||||
|
||||
if id.Username != relayID+"."+c.clusterName {
|
||||
return trace.BadParameter("dialed server is the wrong relay (expected %+q, got %+q)", relayID, id.Username)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
|
||||
NextProtos: []string{simpleALPN},
|
||||
ServerName: serverName,
|
||||
|
||||
CipherSuites: c.ciphersuites,
|
||||
MinVersion: tls.VersionTLS12,
|
||||
}
|
||||
tc := tls.Client(nc, tlsConfig)
|
||||
|
||||
explode := make(chan struct{})
|
||||
defuse := context.AfterFunc(ctx, func() {
|
||||
defer close(explode)
|
||||
tc.SetDeadline(time.Unix(1, 0))
|
||||
})
|
||||
defer defuse()
|
||||
|
||||
if err := writeProto(tc, &relaypeeringv1alpha.DialRequest{
|
||||
TargetHostId: dialTarget,
|
||||
ConnectionType: string(tunnelType),
|
||||
Source: addrToProto(src),
|
||||
Destination: addrToProto(dst),
|
||||
}); err != nil {
|
||||
defuse()
|
||||
_ = tc.Close()
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
resp := new(relaypeeringv1alpha.DialResponse)
|
||||
if err := readProto(tc, resp); err != nil {
|
||||
defuse()
|
||||
_ = tc.Close()
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
if !defuse() {
|
||||
<-explode
|
||||
}
|
||||
tc.SetDeadline(time.Time{})
|
||||
|
||||
if err := trail.FromGRPC(status.FromProto(resp.GetStatus()).Err()); err != nil {
|
||||
_ = tc.Close()
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
return tc, nil
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
// 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 relaypeer
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
|
||||
"github.com/gravitational/trace"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
relaypeeringv1alpha "github.com/gravitational/teleport/gen/proto/go/teleport/relaypeering/v1alpha"
|
||||
"github.com/gravitational/teleport/lib/utils"
|
||||
)
|
||||
|
||||
// simpleALPN is the ALPN protocol name for the bespoke protocol used by peer
|
||||
// connections. Future variants of the protocol should use a different ALPN
|
||||
// protocol name.
|
||||
const simpleALPN = "teleport-relaypeer"
|
||||
|
||||
// The teleport-relaypeer protocol consists of a DialRequest message sent by the
|
||||
// client followed by a DialResponse message sent by the server, containing a
|
||||
// google.rpc.Status. If the status is ok, the data for the connection will then
|
||||
// follow.
|
||||
//
|
||||
// Messages are sent in their protobuf wire format, prefixed by a little endian
|
||||
// 32 bit size. Messages must be smaller than maxMessageSize (128KiB).
|
||||
|
||||
const maxMessageSize = 128 * 1024
|
||||
|
||||
func readProto(r io.Reader, m proto.Message) error {
|
||||
var sizeBuf [4]byte
|
||||
if _, err := io.ReadFull(r, sizeBuf[:]); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
size := binary.LittleEndian.Uint32(sizeBuf[:])
|
||||
if size > maxMessageSize {
|
||||
return trace.LimitExceeded("bad size")
|
||||
}
|
||||
|
||||
msgBuf := make([]byte, size)
|
||||
if _, err := io.ReadFull(r, msgBuf); err != nil {
|
||||
if errors.Is(err, io.EOF) {
|
||||
return trace.Wrap(io.ErrUnexpectedEOF)
|
||||
}
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
if err := proto.Unmarshal(msgBuf, m); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeProto(w io.Writer, m proto.Message) error {
|
||||
msgBuf, err := proto.Marshal(m)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(msgBuf) > maxMessageSize {
|
||||
return trace.LimitExceeded("bad size")
|
||||
}
|
||||
var sizeBuf [4]byte
|
||||
binary.LittleEndian.PutUint32(sizeBuf[:], uint32(len(msgBuf)))
|
||||
if _, err := w.Write(sizeBuf[:]); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
if _, err := w.Write(msgBuf); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func addrToProto(a net.Addr) *relaypeeringv1alpha.Addr {
|
||||
if a == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &relaypeeringv1alpha.Addr{
|
||||
Network: a.Network(),
|
||||
Addr: a.String(),
|
||||
}
|
||||
}
|
||||
|
||||
func addrFromProto(a *relaypeeringv1alpha.Addr) net.Addr {
|
||||
if a == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &utils.NetAddr{
|
||||
AddrNetwork: a.GetNetwork(),
|
||||
Addr: a.GetAddr(),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,304 @@
|
||||
// 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 relaypeer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net"
|
||||
"slices"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gravitational/trace"
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
"github.com/gravitational/teleport/api/trail"
|
||||
"github.com/gravitational/teleport/api/types"
|
||||
relaypeeringv1alpha "github.com/gravitational/teleport/gen/proto/go/teleport/relaypeering/v1alpha"
|
||||
"github.com/gravitational/teleport/lib/tlsca"
|
||||
"github.com/gravitational/teleport/lib/utils"
|
||||
)
|
||||
|
||||
type dialFunc = func(ctx context.Context, dialTarget string, tunnelType types.TunnelType, src, dst net.Addr) (net.Conn, error)
|
||||
|
||||
// ServerConfig contains parameters for [NewServer].
|
||||
type ServerConfig struct {
|
||||
Log *slog.Logger
|
||||
|
||||
GetCertificate func(ctx context.Context) (*tls.Certificate, error)
|
||||
GetPool func(ctx context.Context) (*x509.CertPool, error)
|
||||
Ciphersuites []uint16
|
||||
|
||||
// LocalDial should dial the given target host (in "<host ID>.<cluster
|
||||
// name>" form) for a given tunnel type, returning a connection with the
|
||||
// given source (remote) address and destination (local) address.
|
||||
LocalDial dialFunc
|
||||
}
|
||||
|
||||
// NewServer creates a [Server] with a given configuration.
|
||||
func NewServer(cfg ServerConfig) (*Server, error) {
|
||||
if cfg.Log == nil {
|
||||
return nil, trace.BadParameter("missing Log")
|
||||
}
|
||||
if cfg.GetCertificate == nil {
|
||||
return nil, trace.BadParameter("missing GetCertificate")
|
||||
}
|
||||
if cfg.GetPool == nil {
|
||||
return nil, trace.BadParameter("missing GetPool")
|
||||
}
|
||||
if cfg.LocalDial == nil {
|
||||
return nil, trace.BadParameter("missing LocalDial")
|
||||
}
|
||||
ctx, ctxCancel := context.WithCancel(context.Background())
|
||||
return &Server{
|
||||
log: cfg.Log,
|
||||
|
||||
getCertificate: cfg.GetCertificate,
|
||||
getPool: cfg.GetPool,
|
||||
ciphersuites: cfg.Ciphersuites,
|
||||
|
||||
localDial: cfg.LocalDial,
|
||||
|
||||
ctx: ctx,
|
||||
ctxCancel: ctxCancel,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Server manages listeners and accepts connections for the relay peering dial
|
||||
// protocol, used by other relays in the same relay group to bounce connections
|
||||
// for which the local relay hopefully has a tunnel but the peer relay does not.
|
||||
// It implements the server side of the relay peering dial protocol. The
|
||||
// relay_server heartbeat for the local relay should be advertising a peer
|
||||
// address that lets the other relays reach the listener (or listeners) of this
|
||||
// server.
|
||||
type Server struct {
|
||||
log *slog.Logger
|
||||
|
||||
getCertificate func(ctx context.Context) (*tls.Certificate, error)
|
||||
getPool func(ctx context.Context) (*x509.CertPool, error)
|
||||
ciphersuites []uint16
|
||||
|
||||
localDial dialFunc
|
||||
|
||||
mu sync.Mutex
|
||||
|
||||
wg sync.WaitGroup
|
||||
|
||||
// ctx should only be canceled while holding mu to synchronize adding
|
||||
// connections and listeners to the maps and closing them, as well as adding
|
||||
// tasks to wg, so it should not be externally cancelable.
|
||||
ctx context.Context
|
||||
ctxCancel context.CancelFunc
|
||||
|
||||
tlsListeners map[net.Listener]struct{}
|
||||
conns map[io.Closer]struct{}
|
||||
}
|
||||
|
||||
func (s *Server) ServeTLSListener(l net.Listener) error {
|
||||
defer l.Close()
|
||||
|
||||
s.mu.Lock()
|
||||
if s.ctx.Err() != nil {
|
||||
s.mu.Unlock()
|
||||
return trace.Errorf("server is already terminating")
|
||||
}
|
||||
|
||||
s.wg.Add(1)
|
||||
defer s.wg.Done()
|
||||
|
||||
if s.tlsListeners == nil {
|
||||
s.tlsListeners = make(map[net.Listener]struct{})
|
||||
}
|
||||
s.tlsListeners[l] = struct{}{}
|
||||
s.mu.Unlock()
|
||||
|
||||
defer func() {
|
||||
s.mu.Lock()
|
||||
delete(s.tlsListeners, l)
|
||||
s.mu.Unlock()
|
||||
}()
|
||||
|
||||
var tempDelay time.Duration
|
||||
for {
|
||||
if s.ctx.Err() != nil {
|
||||
s.log.DebugContext(s.ctx, "Exiting due to requested termination")
|
||||
return nil
|
||||
}
|
||||
|
||||
c, err := l.Accept()
|
||||
if err != nil {
|
||||
if s.ctx.Err() != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if tempErr := *new(interface{ Temporary() bool }); errors.As(err, &tempErr) && tempErr.Temporary() {
|
||||
tempDelay = max(5*time.Millisecond, min(2*tempDelay, time.Second))
|
||||
select {
|
||||
case <-time.After(tempDelay):
|
||||
case <-s.ctx.Done():
|
||||
}
|
||||
continue
|
||||
}
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
tempDelay = 0
|
||||
|
||||
s.mu.Lock()
|
||||
if s.ctx.Err() != nil {
|
||||
// a connection sneaked by right before we closed the listener
|
||||
s.mu.Unlock()
|
||||
_ = c.Close()
|
||||
continue
|
||||
}
|
||||
if s.conns == nil {
|
||||
s.conns = make(map[io.Closer]struct{})
|
||||
}
|
||||
s.conns[c] = struct{}{}
|
||||
s.mu.Unlock()
|
||||
|
||||
s.wg.Add(1)
|
||||
go func() {
|
||||
defer s.wg.Done()
|
||||
defer func() {
|
||||
s.mu.Lock()
|
||||
delete(s.conns, c)
|
||||
s.mu.Unlock()
|
||||
}()
|
||||
err := s.handleTLSConnection(c)
|
||||
s.log.DebugContext(context.Background(), "Finished handling peer connection", "error", err)
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) Close() error {
|
||||
s.mu.Lock()
|
||||
tlsListeners := s.tlsListeners
|
||||
s.tlsListeners = nil
|
||||
conns := s.conns
|
||||
s.conns = nil
|
||||
s.mu.Unlock()
|
||||
|
||||
for l := range tlsListeners {
|
||||
_ = l.Close()
|
||||
}
|
||||
for c := range conns {
|
||||
_ = c.Close()
|
||||
}
|
||||
|
||||
s.wg.Wait()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) handleTLSConnection(nc net.Conn) error {
|
||||
handshakeDeadline := time.Now().Add(30 * time.Second)
|
||||
handshakeCtx, cancel := context.WithDeadline(context.Background(), handshakeDeadline)
|
||||
defer cancel()
|
||||
|
||||
cert, err := s.getCertificate(handshakeCtx)
|
||||
if err != nil {
|
||||
_ = nc.Close()
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
pool, err := s.getPool(handshakeCtx)
|
||||
if err != nil {
|
||||
_ = nc.Close()
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
tlsConfig := &tls.Config{
|
||||
GetCertificate: func(*tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
return cert, nil
|
||||
},
|
||||
VerifyConnection: func(cs tls.ConnectionState) error {
|
||||
if cs.NegotiatedProtocol == "" {
|
||||
return trace.NotImplemented("relay peering protocol not supported")
|
||||
}
|
||||
if len(cs.VerifiedChains) < 1 {
|
||||
return trace.AccessDenied("missing or invalid client certificate")
|
||||
}
|
||||
|
||||
id, err := tlsca.FromSubject(cs.VerifiedChains[0][0].Subject, cs.VerifiedChains[0][0].NotAfter)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
if !slices.Contains(id.Groups, string(types.RoleRelay)) &&
|
||||
!slices.Contains(id.SystemRoles, string(types.RoleRelay)) {
|
||||
return trace.BadParameter("client is not a relay (roles %+q, system roles %+q)", id.Groups, id.SystemRoles)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
NextProtos: []string{simpleALPN},
|
||||
|
||||
ClientAuth: tls.RequireAndVerifyClientCert,
|
||||
ClientCAs: pool,
|
||||
|
||||
InsecureSkipVerify: false,
|
||||
|
||||
MinVersion: tls.VersionTLS12,
|
||||
CipherSuites: s.ciphersuites,
|
||||
SessionTicketsDisabled: true,
|
||||
}
|
||||
|
||||
tc := tls.Server(nc, tlsConfig)
|
||||
defer tc.Close()
|
||||
|
||||
if err := tc.HandshakeContext(handshakeCtx); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
// the only possible negotiated protocol is here is the only supported one,
|
||||
// "teleport-relaypeer"
|
||||
|
||||
tc.SetDeadline(handshakeDeadline)
|
||||
|
||||
req := new(relaypeeringv1alpha.DialRequest)
|
||||
if err := readProto(tc, req); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
lc, err := s.localDial(
|
||||
handshakeCtx,
|
||||
req.GetTargetHostId(),
|
||||
types.TunnelType(req.GetConnectionType()),
|
||||
addrFromProto(req.GetSource()),
|
||||
addrFromProto(req.GetDestination()),
|
||||
)
|
||||
if err != nil {
|
||||
_ = writeProto(tc, &relaypeeringv1alpha.DialResponse{
|
||||
Status: status.Convert(trail.ToGRPC(err)).Proto(),
|
||||
})
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
defer lc.Close()
|
||||
|
||||
if err := writeProto(tc, &relaypeeringv1alpha.DialResponse{
|
||||
Status: nil, // i.e. status.Convert(error(nil)).Proto()
|
||||
}); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
tc.SetDeadline(time.Time{})
|
||||
|
||||
return utils.ProxyConn(context.Background(), lc, tc)
|
||||
}
|
||||
@@ -48,6 +48,7 @@ var (
|
||||
ListenerProxyPeer = ListenerType(teleport.Component(teleport.ComponentProxy, "peer"))
|
||||
ListenerMetrics = ListenerType(teleport.ComponentMetrics)
|
||||
ListenerWindowsDesktop = ListenerType(teleport.ComponentWindowsDesktop)
|
||||
ListenerRelayPeer = ListenerType(teleport.Component(teleport.ComponentRelay, "peer"))
|
||||
ListenerRelayTunnel = ListenerType(teleport.Component(teleport.ComponentRelay, "tunnel"))
|
||||
)
|
||||
|
||||
|
||||
@@ -21,6 +21,8 @@ import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"log/slog"
|
||||
"net"
|
||||
"net/netip"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/google/uuid"
|
||||
@@ -38,8 +40,10 @@ import (
|
||||
relaytunnelv1alpha "github.com/gravitational/teleport/gen/proto/go/teleport/relaytunnel/v1alpha"
|
||||
"github.com/gravitational/teleport/lib/auth/authclient"
|
||||
multiplexergrpc "github.com/gravitational/teleport/lib/multiplexer/grpc"
|
||||
"github.com/gravitational/teleport/lib/relaypeer"
|
||||
"github.com/gravitational/teleport/lib/relaytunnel"
|
||||
"github.com/gravitational/teleport/lib/srv"
|
||||
"github.com/gravitational/teleport/lib/utils"
|
||||
)
|
||||
|
||||
func (process *TeleportProcess) initRelay() {
|
||||
@@ -110,6 +114,28 @@ func (process *TeleportProcess) runRelayService() error {
|
||||
TargetConnectionCount: process.Config.Relay.TargetConnectionCount,
|
||||
})
|
||||
|
||||
peerServer, err := relaypeer.NewServer(relaypeer.ServerConfig{
|
||||
Log: sublogger("peer_server"),
|
||||
|
||||
GetCertificate: func(ctx context.Context) (*tls.Certificate, error) {
|
||||
return conn.serverGetCertificate()
|
||||
},
|
||||
GetPool: func(ctx context.Context) (*x509.CertPool, error) {
|
||||
pool, _, err := authclient.ClientCertPool(ctx, accessPoint, conn.clusterName, apitypes.HostCA)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
return pool, nil
|
||||
},
|
||||
Ciphersuites: process.Config.CipherSuites,
|
||||
|
||||
LocalDial: tunnelServer.Dial,
|
||||
})
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
defer peerServer.Close()
|
||||
|
||||
tunnelListener, err := process.importOrCreateListener(ListenerRelayTunnel, process.Config.Relay.TunnelListenAddr.String())
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
@@ -117,6 +143,29 @@ func (process *TeleportProcess) runRelayService() error {
|
||||
defer tunnelListener.Close()
|
||||
go tunnelGRPCServer.Serve(tunnelListener)
|
||||
|
||||
peerListener, err := process.importOrCreateListener(ListenerRelayPeer, process.Config.Relay.PeerListenAddr.String())
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
defer peerListener.Close()
|
||||
go peerServer.ServeTLSListener(peerListener)
|
||||
|
||||
peerPublicAddr := process.Config.Relay.PeerPublicAddr
|
||||
if peerPublicAddr == "" {
|
||||
peerListenerPort := process.Config.Relay.PeerListenAddr.Port()
|
||||
if a, _ := peerListener.Addr().(*net.TCPAddr); a != nil {
|
||||
// handle the case where the server was configured to bind on port 0
|
||||
peerListenerPort = uint16(a.Port)
|
||||
}
|
||||
|
||||
hostIP, err := utils.GuessHostIP()
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
hostNetIP, _ := netip.AddrFromSlice(hostIP)
|
||||
peerPublicAddr = netip.AddrPortFrom(hostNetIP.Unmap(), peerListenerPort).String()
|
||||
}
|
||||
|
||||
nonce := uuid.NewString()
|
||||
var relayServer atomic.Pointer[presencev1.RelayServer]
|
||||
relayServer.Store(&presencev1.RelayServer{
|
||||
@@ -129,6 +178,7 @@ func (process *TeleportProcess) runRelayService() error {
|
||||
Spec: &presencev1.RelayServer_Spec{
|
||||
Hostname: process.Config.Hostname,
|
||||
RelayGroup: process.Config.Relay.RelayGroup,
|
||||
PeerAddr: peerPublicAddr,
|
||||
Nonce: nonce,
|
||||
},
|
||||
})
|
||||
@@ -196,6 +246,12 @@ func (process *TeleportProcess) runRelayService() error {
|
||||
_ = tunnelServer.Close()
|
||||
return nil
|
||||
})
|
||||
eg.Go(func() error {
|
||||
// TODO(espadolini): let connections continue (for a time?) before
|
||||
// abruptly terminating them right after the shutdown delay
|
||||
_ = peerServer.Close()
|
||||
return nil
|
||||
})
|
||||
warnOnErr(egCtx, eg.Wait(), log)
|
||||
|
||||
warnOnErr(ctx, hb.Close(), log)
|
||||
|
||||
@@ -44,6 +44,10 @@ type RelayConfig struct {
|
||||
// PeerListenAddr is the listen address for the peer listener.
|
||||
PeerListenAddr netip.AddrPort
|
||||
|
||||
// PeerPublicAddr, if set, is the public address for the peer listener, in
|
||||
// host:port format.
|
||||
PeerPublicAddr string
|
||||
|
||||
// TunnelListenAddr is the listen address for the tunnel listener.
|
||||
TunnelListenAddr netip.AddrPort
|
||||
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
// Teleport
|
||||
// Copyright (C) 2024 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/>.
|
||||
|
||||
syntax = "proto3";
|
||||
|
||||
package teleport.relaypeering.v1alpha;
|
||||
|
||||
import "google/rpc/status.proto";
|
||||
|
||||
option go_package = "github.com/gravitational/teleport/gen/proto/go/teleport/relaypeering/v1alpha;relaypeeringv1alpha";
|
||||
|
||||
// the message sent by the client at the beginning of a connection
|
||||
message DialRequest {
|
||||
// the target agent for the connection attempt; should be "<host id>.<cluster name>"
|
||||
string target_host_id = 1;
|
||||
// the type of the connection as defined by api/types.TunnelType ("node",
|
||||
// "app", "kube"...)
|
||||
string connection_type = 2;
|
||||
|
||||
Addr source = 3;
|
||||
Addr destination = 4;
|
||||
}
|
||||
|
||||
// the message sent by the server as a response to a DialRequest; in case of
|
||||
// success (status is missing or reports "ok") the connection data follows on
|
||||
// the same stream, otherwise the stream ends and any data sent by the client is
|
||||
// to be treated as silently discarded
|
||||
message DialResponse {
|
||||
google.rpc.Status status = 1;
|
||||
}
|
||||
|
||||
// a stringy Go net.Addr, usually converted to and from lib/utils.NetAddr
|
||||
message Addr {
|
||||
// likely always "tcp"
|
||||
string network = 1;
|
||||
// depending on the network, likely "<ip address>:<port>"
|
||||
string addr = 2;
|
||||
}
|
||||
Reference in New Issue
Block a user