mirror of
https://github.com/gravitational/teleport.git
synced 2026-11-01 13:39:37 +08:00
Sets `http.Server.IdleTimeout` and `http.Client.IdleConnTimeout` on clients and servers which didn't have them set. A default of 360s was chosen to be on par with the default of an NLB without being identical. This was added as another safety measure to prevent leaking any idle connections indefinitely as seen in #22757. `apidefaults.DefaultDialTimeout` was also renamed to `apidefaults.DefaultIOTimeout` to better reflect its usage.
209 lines
6.7 KiB
Go
209 lines
6.7 KiB
Go
/*
|
|
Copyright 2021 Gravitational, Inc.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package alpnproxyauth
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"strings"
|
|
|
|
"github.com/gravitational/trace"
|
|
"github.com/sirupsen/logrus"
|
|
"go.opentelemetry.io/otel/attribute"
|
|
oteltrace "go.opentelemetry.io/otel/trace"
|
|
|
|
"github.com/gravitational/teleport/api/defaults"
|
|
apiutils "github.com/gravitational/teleport/api/utils"
|
|
"github.com/gravitational/teleport/lib/multiplexer"
|
|
"github.com/gravitational/teleport/lib/reversetunnel"
|
|
"github.com/gravitational/teleport/lib/srv/alpnproxy"
|
|
"github.com/gravitational/teleport/lib/srv/alpnproxy/common"
|
|
"github.com/gravitational/teleport/lib/utils"
|
|
)
|
|
|
|
type sitesGetter interface {
|
|
GetSites() ([]reversetunnel.RemoteSite, error)
|
|
}
|
|
|
|
// NewAuthProxyDialerService create new instance of AuthProxyDialerService.
|
|
func NewAuthProxyDialerService(reverseTunnelServer sitesGetter, localClusterName string, authServers []string, proxySigner multiplexer.PROXYHeaderSigner, log logrus.FieldLogger, tracer oteltrace.Tracer) *AuthProxyDialerService {
|
|
return &AuthProxyDialerService{
|
|
reverseTunnelServer: reverseTunnelServer,
|
|
localClusterName: localClusterName,
|
|
authServers: authServers,
|
|
proxySigner: proxySigner,
|
|
log: log,
|
|
tracer: tracer,
|
|
}
|
|
}
|
|
|
|
// AuthProxyDialerService allows dialing local/remote auth service base on SNI value encoded as destination auth
|
|
// cluster name and ALPN set to teleport-auth protocol.
|
|
type AuthProxyDialerService struct {
|
|
reverseTunnelServer sitesGetter
|
|
localClusterName string
|
|
authServers []string
|
|
proxySigner multiplexer.PROXYHeaderSigner
|
|
log logrus.FieldLogger
|
|
tracer oteltrace.Tracer
|
|
}
|
|
|
|
func (s *AuthProxyDialerService) HandleConnection(ctx context.Context, conn net.Conn, connInfo alpnproxy.ConnectionInfo) error {
|
|
defer conn.Close()
|
|
clusterName, err := getClusterName(connInfo)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
authConn, err := s.dialAuthServer(ctx, clusterName, conn.RemoteAddr(), conn.LocalAddr())
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
defer authConn.Close()
|
|
|
|
if err := s.proxyConn(ctx, conn, authConn); err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func getClusterName(info alpnproxy.ConnectionInfo) (string, error) {
|
|
if len(info.ALPN) == 0 {
|
|
return "", trace.NotFound("missing ALPN value")
|
|
}
|
|
protocol := info.ALPN[0]
|
|
if !strings.HasPrefix(protocol, string(common.ProtocolAuth)) {
|
|
return "", trace.BadParameter("auth routing prefix not found")
|
|
}
|
|
routeToCluster := strings.TrimPrefix(protocol, string(common.ProtocolAuth))
|
|
cn, err := apiutils.DecodeClusterName(routeToCluster)
|
|
if err != nil {
|
|
return "", trace.Wrap(err)
|
|
}
|
|
return cn, nil
|
|
}
|
|
|
|
func (s *AuthProxyDialerService) dialAuthServer(ctx context.Context, clusterNameFromSNI string, clientSrcAddr, clientDstAddr net.Addr) (net.Conn, error) {
|
|
if clusterNameFromSNI == s.localClusterName {
|
|
return s.dialLocalAuthServer(ctx, clientSrcAddr, clientDstAddr)
|
|
}
|
|
if s.reverseTunnelServer != nil {
|
|
return s.dialRemoteAuthServer(ctx, clusterNameFromSNI, clientSrcAddr, clientDstAddr)
|
|
}
|
|
return nil, trace.NotFound("auth server for %q cluster name not found", clusterNameFromSNI)
|
|
}
|
|
|
|
func (s *AuthProxyDialerService) dialLocalAuthServer(ctx context.Context, clientSrcAddr, clientDstAddr net.Addr) (net.Conn, error) {
|
|
ctx, span := s.tracer.Start(ctx, "authProxyDialerService/dialLocalAuthServer",
|
|
oteltrace.WithSpanKind(oteltrace.SpanKindServer),
|
|
oteltrace.WithAttributes(
|
|
attribute.String("src_addr", fmt.Sprintf("%v", clientSrcAddr)),
|
|
attribute.String("dst_addr", fmt.Sprintf("%v", clientDstAddr)),
|
|
attribute.String("cluster_name", s.localClusterName),
|
|
))
|
|
defer span.End()
|
|
|
|
if len(s.authServers) == 0 {
|
|
return nil, trace.NotFound("empty auth servers list")
|
|
}
|
|
|
|
addr := utils.ChooseRandomString(s.authServers)
|
|
d := &net.Dialer{
|
|
Timeout: defaults.DefaultIOTimeout,
|
|
}
|
|
conn, err := d.DialContext(ctx, "tcp", addr)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
span.AddEvent("dialed remote server")
|
|
|
|
// We'll write signed PROXY header to the outgoing connection to securely
|
|
// propagate observed client ip to the auth server.
|
|
if s.proxySigner != nil && clientSrcAddr != nil && clientDstAddr != nil {
|
|
b, err := s.proxySigner.SignPROXYHeader(clientSrcAddr, clientDstAddr)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err, "could not create signed PROXY header")
|
|
}
|
|
|
|
_, err = conn.Write(b)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err, "could not write PROXY line to remote connection")
|
|
}
|
|
span.AddEvent("wrote signed PROXY header")
|
|
}
|
|
|
|
return conn, nil
|
|
}
|
|
|
|
func (s *AuthProxyDialerService) dialRemoteAuthServer(ctx context.Context, clusterName string, clientSrcAddr, clientDstAddr net.Addr) (net.Conn, error) {
|
|
_, span := s.tracer.Start(ctx, "authProxyDialerService/dialRemoteAuthServer",
|
|
oteltrace.WithSpanKind(oteltrace.SpanKindServer),
|
|
oteltrace.WithAttributes(
|
|
attribute.String("src_addr", fmt.Sprintf("%v", clientSrcAddr)),
|
|
attribute.String("dst_addr", fmt.Sprintf("%v", clientDstAddr)),
|
|
attribute.String("cluster_name", clusterName),
|
|
))
|
|
defer span.End()
|
|
|
|
sites, err := s.reverseTunnelServer.GetSites()
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
for _, site := range sites {
|
|
if site.GetName() != clusterName {
|
|
continue
|
|
}
|
|
conn, err := site.DialAuthServer(reversetunnel.DialParams{From: clientSrcAddr, OriginalClientDstAddr: clientDstAddr})
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return conn, nil
|
|
}
|
|
return nil, trace.NotFound("cluster name %q not found", clusterName)
|
|
}
|
|
|
|
func (s *AuthProxyDialerService) proxyConn(ctx context.Context, upstreamConn, downstreamConn net.Conn) error {
|
|
errC := make(chan error, 2)
|
|
go func() {
|
|
defer upstreamConn.Close()
|
|
defer downstreamConn.Close()
|
|
_, err := io.Copy(downstreamConn, upstreamConn)
|
|
errC <- trace.Wrap(err)
|
|
|
|
}()
|
|
go func() {
|
|
defer upstreamConn.Close()
|
|
defer downstreamConn.Close()
|
|
_, err := io.Copy(upstreamConn, downstreamConn)
|
|
errC <- trace.Wrap(err)
|
|
}()
|
|
var errs []error
|
|
for i := 0; i < 2; i++ {
|
|
select {
|
|
case <-ctx.Done():
|
|
return trace.Wrap(ctx.Err())
|
|
case err := <-errC:
|
|
if err != nil && !utils.IsOKNetworkError(err) {
|
|
errs = append(errs, err)
|
|
}
|
|
}
|
|
}
|
|
return trace.NewAggregate(errs...)
|
|
}
|