mirror of
https://github.com/gravitational/teleport.git
synced 2026-11-05 03:33:24 +08:00
To prevent users from being able to directly connect to registered OpenSSH (Agentless) nodes, the OpenSSH CA was introduced. When connecting to Agentless nodes, the Proxy will forward the connection so the session can be recorded. The forwarding SSH server is configured to authenticate to the target node with a certificate signed by OpenSSH CA, which users do not have access to.
202 lines
7.0 KiB
Go
202 lines
7.0 KiB
Go
/*
|
|
Copyright 2016 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 reversetunnel
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"time"
|
|
|
|
"golang.org/x/crypto/ssh"
|
|
|
|
"github.com/gravitational/teleport/api/types"
|
|
"github.com/gravitational/teleport/lib/auth"
|
|
"github.com/gravitational/teleport/lib/proxy/peer"
|
|
"github.com/gravitational/teleport/lib/services"
|
|
"github.com/gravitational/teleport/lib/teleagent"
|
|
)
|
|
|
|
// DialParams is a list of parameters used to Dial to a node within a cluster.
|
|
type DialParams struct {
|
|
// From is the source address.
|
|
From net.Addr
|
|
|
|
// To is the destination address.
|
|
To net.Addr
|
|
|
|
// GetUserAgent gets an SSH agent for use in connecting to the remote host. Used by the
|
|
// forwarding proxy.
|
|
GetUserAgent teleagent.Getter
|
|
|
|
// AgentlessSigner is used for authenticating to the remote host when it is an
|
|
// agentless node.
|
|
AgentlessSigner ssh.Signer
|
|
|
|
// Address is used by the forwarding proxy to generate a host certificate for
|
|
// the target node. This is needed because while dialing occurs via IP
|
|
// address, tsh thinks it's connecting via DNS name and that's how it
|
|
// validates the host certificate.
|
|
Address string
|
|
|
|
// Principals are additional principals that need to be added to the host
|
|
// certificate. Used by the recording proxy to correctly generate a host
|
|
// certificate.
|
|
Principals []string
|
|
|
|
// ServerID the hostUUID.clusterName of a Teleport node. Used with nodes
|
|
// that are connected over a reverse tunnel.
|
|
ServerID string
|
|
|
|
// ProxyIDs is a list of proxy ids the node is connected to.
|
|
ProxyIDs []string
|
|
|
|
// ConnType is the type of connection requested, either node or application.
|
|
// Only used when connecting through a tunnel.
|
|
ConnType types.TunnelType
|
|
|
|
// TargetServer is the host that the connection is being established for.
|
|
// It **MUST** only be populated when the target is a teleport ssh server
|
|
// or an agentless server.
|
|
TargetServer types.Server
|
|
|
|
// FromPeerProxy indicates that the dial request is being tunneled from
|
|
// a peer proxy.
|
|
FromPeerProxy bool
|
|
|
|
// TeleportVersion shows version of the target node, if we know that it's teleport node.
|
|
TeleportVersion string
|
|
|
|
// OriginalClientDstAddr is used in PROXY headers to show where client originally contacted Teleport infrastructure
|
|
OriginalClientDstAddr net.Addr
|
|
}
|
|
|
|
func (params DialParams) String() string {
|
|
to := params.To.String()
|
|
if to == "" {
|
|
to = params.ServerID
|
|
}
|
|
return fmt.Sprintf("from: %q to: %q", params.From, to)
|
|
}
|
|
|
|
func stringOrEmpty(addr net.Addr) string {
|
|
if addr == nil {
|
|
return ""
|
|
}
|
|
return addr.String()
|
|
}
|
|
|
|
// shouldDialAndForward returns whether a connection should be proxied
|
|
// and forwarded or not.
|
|
func shouldDialAndForward(params DialParams, recConfig types.SessionRecordingConfig) bool {
|
|
// connection is already being tunneled, do not forward
|
|
if params.FromPeerProxy {
|
|
return false
|
|
}
|
|
// the node is an agentless node, the connection must be forwarded
|
|
if params.TargetServer != nil && params.TargetServer.GetSubKind() == types.SubKindOpenSSHNode {
|
|
return true
|
|
}
|
|
// proxy session recording mode is being used and an SSH session
|
|
// is being requested, the connection must be forwarded
|
|
if params.ConnType == types.NodeTunnel && services.IsRecordAtProxy(recConfig.GetMode()) {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// RemoteSite represents remote teleport site that can be accessed via
|
|
// teleport tunnel or directly by proxy
|
|
//
|
|
// There are two implementations of this interface: local and remote sites.
|
|
type RemoteSite interface {
|
|
// DialAuthServer returns a net.Conn to the Auth Server of a site.
|
|
DialAuthServer(DialParams) (conn net.Conn, err error)
|
|
// Dial dials any address within the site network, in terminating
|
|
// mode it uses local instance of forwarding server to terminate
|
|
// and record the connection.
|
|
Dial(DialParams) (conn net.Conn, err error)
|
|
// DialTCP dials any address within the site network and
|
|
// ignores recording mode, used in components that need direct dialer.
|
|
DialTCP(DialParams) (conn net.Conn, err error)
|
|
// GetLastConnected returns last time the remote site was seen connected
|
|
GetLastConnected() time.Time
|
|
// GetName returns site name (identified by authority domain's name)
|
|
GetName() string
|
|
// GetStatus returns status of this site (either offline or connected)
|
|
GetStatus() string
|
|
// GetClient returns client connected to remote auth server
|
|
GetClient() (auth.ClientI, error)
|
|
// CachingAccessPoint returns access point that is lightweight
|
|
// but is resilient to auth server crashes
|
|
CachingAccessPoint() (auth.RemoteProxyAccessPoint, error)
|
|
// NodeWatcher returns the node watcher that maintains the node set for the site
|
|
NodeWatcher() (*services.NodeWatcher, error)
|
|
// GetTunnelsCount returns the amount of active inbound tunnels
|
|
// from the remote cluster
|
|
GetTunnelsCount() int
|
|
// IsClosed reports whether this RemoteSite has been closed and should no
|
|
// longer be used.
|
|
IsClosed() bool
|
|
// Closer allows the site to be closed
|
|
io.Closer
|
|
}
|
|
|
|
// Tunnel provides access to connected local or remote clusters
|
|
// using unified interface.
|
|
type Tunnel interface {
|
|
// GetSites returns a list of connected remote sites
|
|
GetSites() ([]RemoteSite, error)
|
|
// GetSite returns remote site this node belongs to
|
|
GetSite(domainName string) (RemoteSite, error)
|
|
}
|
|
|
|
// Server is a TCP/IP SSH server which listens on an SSH endpoint and remote/local
|
|
// sites connect and register with it.
|
|
type Server interface {
|
|
Tunnel
|
|
// Start starts server
|
|
Start() error
|
|
// Close closes server's operations immediately
|
|
Close() error
|
|
// DrainConnections closes listeners and begins draining connections without
|
|
// closing open connections.
|
|
DrainConnections(context.Context) error
|
|
// Shutdown performs graceful server shutdown closing open connections.
|
|
Shutdown(context.Context) error
|
|
// Wait waits for server to close all outstanding operations
|
|
Wait(ctx context.Context)
|
|
// GetProxyPeerClient returns the proxy peer client
|
|
GetProxyPeerClient() *peer.Client
|
|
}
|
|
|
|
const (
|
|
// NoApplicationTunnel is the error message returned when application
|
|
// reverse tunnel cannot be found.
|
|
//
|
|
// It usually happens when an app agent has shut down (or crashed) but
|
|
// hasn't expired from the backend yet.
|
|
NoApplicationTunnel = "could not find reverse tunnel, check that Application Service agent proxying this application is up and running"
|
|
// NoDatabaseTunnel is the error message returned when database reverse
|
|
// tunnel cannot be found.
|
|
//
|
|
// It usually happens when a database agent has shut down (or crashed) but
|
|
// hasn't expired from the backend yet.
|
|
NoDatabaseTunnel = "could not find reverse tunnel, check that Database Service agent proxying this database is up and running"
|
|
)
|