mirror of
https://github.com/tailscale/tailscale.git
synced 2025-01-07 08:07:42 +00:00
ssh/tailssh: add support for remote/reverse port forwarding
This basically allows running services on the SSH client and reaching them from the SSH server during the session. Updates #6575 Signed-off-by: Maisem Ali <maisem@tailscale.com>
This commit is contained in:
parent
62130e6b68
commit
2e0aa151c9
@ -422,6 +422,7 @@ func (srv *server) newConn() (*conn, error) {
|
|||||||
c := &conn{srv: srv}
|
c := &conn{srv: srv}
|
||||||
now := srv.now()
|
now := srv.now()
|
||||||
c.connID = fmt.Sprintf("ssh-conn-%s-%02x", now.UTC().Format("20060102T150405"), randBytes(5))
|
c.connID = fmt.Sprintf("ssh-conn-%s-%02x", now.UTC().Format("20060102T150405"), randBytes(5))
|
||||||
|
fwdHandler := &ssh.ForwardedTCPHandler{}
|
||||||
c.Server = &ssh.Server{
|
c.Server = &ssh.Server{
|
||||||
Version: "Tailscale",
|
Version: "Tailscale",
|
||||||
ServerConfigCallback: c.ServerConfig,
|
ServerConfigCallback: c.ServerConfig,
|
||||||
@ -430,8 +431,9 @@ func (srv *server) newConn() (*conn, error) {
|
|||||||
PublicKeyHandler: c.PublicKeyHandler,
|
PublicKeyHandler: c.PublicKeyHandler,
|
||||||
PasswordHandler: c.fakePasswordHandler,
|
PasswordHandler: c.fakePasswordHandler,
|
||||||
|
|
||||||
Handler: c.handleSessionPostSSHAuth,
|
Handler: c.handleSessionPostSSHAuth,
|
||||||
LocalPortForwardingCallback: c.mayForwardLocalPortTo,
|
LocalPortForwardingCallback: c.mayForwardLocalPortTo,
|
||||||
|
ReversePortForwardingCallback: c.mayReversePortForwardTo,
|
||||||
SubsystemHandlers: map[string]ssh.SubsystemHandler{
|
SubsystemHandlers: map[string]ssh.SubsystemHandler{
|
||||||
"sftp": c.handleSessionPostSSHAuth,
|
"sftp": c.handleSessionPostSSHAuth,
|
||||||
},
|
},
|
||||||
@ -441,7 +443,10 @@ func (srv *server) newConn() (*conn, error) {
|
|||||||
ChannelHandlers: map[string]ssh.ChannelHandler{
|
ChannelHandlers: map[string]ssh.ChannelHandler{
|
||||||
"direct-tcpip": ssh.DirectTCPIPHandler,
|
"direct-tcpip": ssh.DirectTCPIPHandler,
|
||||||
},
|
},
|
||||||
RequestHandlers: map[string]ssh.RequestHandler{},
|
RequestHandlers: map[string]ssh.RequestHandler{
|
||||||
|
"tcpip-forward": fwdHandler.HandleSSHRequest,
|
||||||
|
"cancel-tcpip-forward": fwdHandler.HandleSSHRequest,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
ss := c.Server
|
ss := c.Server
|
||||||
for k, v := range ssh.DefaultRequestHandlers {
|
for k, v := range ssh.DefaultRequestHandlers {
|
||||||
@ -463,6 +468,17 @@ func (srv *server) newConn() (*conn, error) {
|
|||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// mayReversePortPortForwardTo reports whether the ctx should be allowed to port forward
|
||||||
|
// to the specified host and port.
|
||||||
|
// TODO(bradfitz/maisem): should we have more checks on host/port?
|
||||||
|
func (c *conn) mayReversePortForwardTo(ctx ssh.Context, destinationHost string, destinationPort uint32) bool {
|
||||||
|
if c.finalAction != nil && c.finalAction.AllowRemotePortForwarding {
|
||||||
|
metricRemotePortForward.Add(1)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// mayForwardLocalPortTo reports whether the ctx should be allowed to port forward
|
// mayForwardLocalPortTo reports whether the ctx should be allowed to port forward
|
||||||
// to the specified host and port.
|
// to the specified host and port.
|
||||||
// TODO(bradfitz/maisem): should we have more checks on host/port?
|
// TODO(bradfitz/maisem): should we have more checks on host/port?
|
||||||
@ -1860,6 +1876,7 @@ func envEq(a, b string) bool {
|
|||||||
metricPolicyChangeKick = clientmetric.NewCounter("ssh_policy_change_kick")
|
metricPolicyChangeKick = clientmetric.NewCounter("ssh_policy_change_kick")
|
||||||
metricSFTP = clientmetric.NewCounter("ssh_sftp_requests")
|
metricSFTP = clientmetric.NewCounter("ssh_sftp_requests")
|
||||||
metricLocalPortForward = clientmetric.NewCounter("ssh_local_port_forward_requests")
|
metricLocalPortForward = clientmetric.NewCounter("ssh_local_port_forward_requests")
|
||||||
|
metricRemotePortForward = clientmetric.NewCounter("ssh_remote_port_forward_requests")
|
||||||
)
|
)
|
||||||
|
|
||||||
// userVisibleError is a wrapper around an error that implements
|
// userVisibleError is a wrapper around an error that implements
|
||||||
|
@ -99,7 +99,8 @@
|
|||||||
// - 60: 2023-04-06: Client understands IsWireGuardOnly
|
// - 60: 2023-04-06: Client understands IsWireGuardOnly
|
||||||
// - 61: 2023-04-18: Client understand SSHAction.SSHRecorderFailureAction
|
// - 61: 2023-04-18: Client understand SSHAction.SSHRecorderFailureAction
|
||||||
// - 62: 2023-05-05: Client can notify control over noise for SSHEventNotificationRequest recording failure events
|
// - 62: 2023-05-05: Client can notify control over noise for SSHEventNotificationRequest recording failure events
|
||||||
const CurrentCapabilityVersion CapabilityVersion = 62
|
// - 63: 2023-06-08: Client understands SSHAction.AllowRemotePortForwarding.
|
||||||
|
const CurrentCapabilityVersion CapabilityVersion = 63
|
||||||
|
|
||||||
type StableID string
|
type StableID string
|
||||||
|
|
||||||
@ -2048,6 +2049,10 @@ type SSHAction struct {
|
|||||||
// to use local port forwarding if requested.
|
// to use local port forwarding if requested.
|
||||||
AllowLocalPortForwarding bool `json:"allowLocalPortForwarding,omitempty"`
|
AllowLocalPortForwarding bool `json:"allowLocalPortForwarding,omitempty"`
|
||||||
|
|
||||||
|
// AllowRemotePortForwarding, if true, allows accepted connections
|
||||||
|
// to use remote port forwarding if requested.
|
||||||
|
AllowRemotePortForwarding bool `json:"allowRemotePortForwarding,omitempty"`
|
||||||
|
|
||||||
// Recorders defines the destinations of the SSH session recorders.
|
// Recorders defines the destinations of the SSH session recorders.
|
||||||
// The recording will be uploaded to http://addr:port/record.
|
// The recording will be uploaded to http://addr:port/record.
|
||||||
Recorders []netip.AddrPort `json:"recorders,omitempty"`
|
Recorders []netip.AddrPort `json:"recorders,omitempty"`
|
||||||
|
@ -408,15 +408,16 @@ func (src *SSHAction) Clone() *SSHAction {
|
|||||||
|
|
||||||
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
|
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
|
||||||
var _SSHActionCloneNeedsRegeneration = SSHAction(struct {
|
var _SSHActionCloneNeedsRegeneration = SSHAction(struct {
|
||||||
Message string
|
Message string
|
||||||
Reject bool
|
Reject bool
|
||||||
Accept bool
|
Accept bool
|
||||||
SessionDuration time.Duration
|
SessionDuration time.Duration
|
||||||
AllowAgentForwarding bool
|
AllowAgentForwarding bool
|
||||||
HoldAndDelegate string
|
HoldAndDelegate string
|
||||||
AllowLocalPortForwarding bool
|
AllowLocalPortForwarding bool
|
||||||
Recorders []netip.AddrPort
|
AllowRemotePortForwarding bool
|
||||||
OnRecordingFailure *SSHRecorderFailureAction
|
Recorders []netip.AddrPort
|
||||||
|
OnRecordingFailure *SSHRecorderFailureAction
|
||||||
}{})
|
}{})
|
||||||
|
|
||||||
// Clone makes a deep copy of SSHPrincipal.
|
// Clone makes a deep copy of SSHPrincipal.
|
||||||
|
@ -940,6 +940,7 @@ func (v SSHActionView) SessionDuration() time.Duration { return v.ж.Ses
|
|||||||
func (v SSHActionView) AllowAgentForwarding() bool { return v.ж.AllowAgentForwarding }
|
func (v SSHActionView) AllowAgentForwarding() bool { return v.ж.AllowAgentForwarding }
|
||||||
func (v SSHActionView) HoldAndDelegate() string { return v.ж.HoldAndDelegate }
|
func (v SSHActionView) HoldAndDelegate() string { return v.ж.HoldAndDelegate }
|
||||||
func (v SSHActionView) AllowLocalPortForwarding() bool { return v.ж.AllowLocalPortForwarding }
|
func (v SSHActionView) AllowLocalPortForwarding() bool { return v.ж.AllowLocalPortForwarding }
|
||||||
|
func (v SSHActionView) AllowRemotePortForwarding() bool { return v.ж.AllowRemotePortForwarding }
|
||||||
func (v SSHActionView) Recorders() views.Slice[netip.AddrPort] { return views.SliceOf(v.ж.Recorders) }
|
func (v SSHActionView) Recorders() views.Slice[netip.AddrPort] { return views.SliceOf(v.ж.Recorders) }
|
||||||
func (v SSHActionView) OnRecordingFailure() *SSHRecorderFailureAction {
|
func (v SSHActionView) OnRecordingFailure() *SSHRecorderFailureAction {
|
||||||
if v.ж.OnRecordingFailure == nil {
|
if v.ж.OnRecordingFailure == nil {
|
||||||
@ -951,15 +952,16 @@ func (v SSHActionView) OnRecordingFailure() *SSHRecorderFailureAction {
|
|||||||
|
|
||||||
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
|
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
|
||||||
var _SSHActionViewNeedsRegeneration = SSHAction(struct {
|
var _SSHActionViewNeedsRegeneration = SSHAction(struct {
|
||||||
Message string
|
Message string
|
||||||
Reject bool
|
Reject bool
|
||||||
Accept bool
|
Accept bool
|
||||||
SessionDuration time.Duration
|
SessionDuration time.Duration
|
||||||
AllowAgentForwarding bool
|
AllowAgentForwarding bool
|
||||||
HoldAndDelegate string
|
HoldAndDelegate string
|
||||||
AllowLocalPortForwarding bool
|
AllowLocalPortForwarding bool
|
||||||
Recorders []netip.AddrPort
|
AllowRemotePortForwarding bool
|
||||||
OnRecordingFailure *SSHRecorderFailureAction
|
Recorders []netip.AddrPort
|
||||||
|
OnRecordingFailure *SSHRecorderFailureAction
|
||||||
}{})
|
}{})
|
||||||
|
|
||||||
// View returns a readonly view of SSHPrincipal.
|
// View returns a readonly view of SSHPrincipal.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user