mirror of
https://github.com/gravitational/teleport
synced 2024-10-19 08:43:58 +00:00
return an error when attempting to join a session of an OpenSSH node (#31472)
* return an error when attempting to join a session of an OpenSSH node * remove item from test plan and note to docs * add test coverage to integration test * fix integration test * fixed linter issue
This commit is contained in:
parent
a50f967d27
commit
5054cb685c
2
.github/ISSUE_TEMPLATE/testplan.md
vendored
2
.github/ISSUE_TEMPLATE/testplan.md
vendored
|
@ -183,8 +183,6 @@ as well as an upgrade of the previous version of Teleport.
|
||||||
- [ ] tsh ssh \<agentless-node-remote-cluster\> ls
|
- [ ] tsh ssh \<agentless-node-remote-cluster\> ls
|
||||||
- [ ] tsh join \<regular-node\>
|
- [ ] tsh join \<regular-node\>
|
||||||
- [ ] tsh join \<node-remote-cluster\>
|
- [ ] tsh join \<node-remote-cluster\>
|
||||||
- [ ] tsh join \<agentless-node\>
|
|
||||||
- [ ] tsh join \<agentless-node-remote-cluster\>
|
|
||||||
- [ ] tsh play \<regular-node\>
|
- [ ] tsh play \<regular-node\>
|
||||||
- [ ] tsh play \<node-remote-cluster\>
|
- [ ] tsh play \<node-remote-cluster\>
|
||||||
- [ ] tsh play \<agentless-node\>
|
- [ ] tsh play \<agentless-node\>
|
||||||
|
|
|
@ -4917,6 +4917,9 @@ message SessionTrackerSpecV1 {
|
||||||
// It's useful for Kubernetes moderated sessions when running in high availabilty
|
// It's useful for Kubernetes moderated sessions when running in high availabilty
|
||||||
// otherwise kube proxy is not able to know which agent runs the session.
|
// otherwise kube proxy is not able to know which agent runs the session.
|
||||||
string HostID = 21 [(gogoproto.jsontag) = "host_id,omitempty"];
|
string HostID = 21 [(gogoproto.jsontag) = "host_id,omitempty"];
|
||||||
|
|
||||||
|
// TargetSubKind is the sub kind of the target server.
|
||||||
|
string TargetSubKind = 22 [(gogoproto.jsontag) = "target_sub_kind,omitempty"];
|
||||||
}
|
}
|
||||||
|
|
||||||
// SessionTrackerPolicySet is a set of RBAC policies held by the session tracker
|
// SessionTrackerPolicySet is a set of RBAC policies held by the session tracker
|
||||||
|
|
|
@ -119,8 +119,12 @@ type SessionTracker interface {
|
||||||
|
|
||||||
// GetLastActive returns the time at which the session was last active (i.e used by any participant).
|
// GetLastActive returns the time at which the session was last active (i.e used by any participant).
|
||||||
GetLastActive() time.Time
|
GetLastActive() time.Time
|
||||||
|
|
||||||
// HostID is the target host id that created the session tracker.
|
// HostID is the target host id that created the session tracker.
|
||||||
GetHostID() string
|
GetHostID() string
|
||||||
|
|
||||||
|
// GetTargetSubKind returns the sub kind of the target server.
|
||||||
|
GetTargetSubKind() string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSessionTracker(spec SessionTrackerSpecV1) (SessionTracker, error) {
|
func NewSessionTracker(spec SessionTrackerSpecV1) (SessionTracker, error) {
|
||||||
|
@ -335,6 +339,11 @@ func (s *SessionTrackerV1) GetLastActive() time.Time {
|
||||||
return last
|
return last
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetTargetSubKind returns the sub kind of the target server.
|
||||||
|
func (s *SessionTrackerV1) GetTargetSubKind() string {
|
||||||
|
return s.Spec.TargetSubKind
|
||||||
|
}
|
||||||
|
|
||||||
// Match checks if a given session tracker matches this filter.
|
// Match checks if a given session tracker matches this filter.
|
||||||
func (f *SessionTrackerFilter) Match(s SessionTracker) bool {
|
func (f *SessionTrackerFilter) Match(s SessionTracker) bool {
|
||||||
if f.Kind != "" && string(s.GetSessionKind()) != f.Kind {
|
if f.Kind != "" && string(s.GetSessionKind()) != f.Kind {
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -16,6 +16,7 @@ and running, but in the long run, we would recommend replacing `sshd` with `tele
|
||||||
|
|
||||||
- RBAC and resource filtering based on [dynamically updated labels](../../management/admin/labels.mdx)
|
- RBAC and resource filtering based on [dynamically updated labels](../../management/admin/labels.mdx)
|
||||||
- [Session recording without SSH connection termination](recording-proxy-mode.mdx)
|
- [Session recording without SSH connection termination](recording-proxy-mode.mdx)
|
||||||
|
- [Session sharing](../../connect-your-client/tsh.mdx)
|
||||||
- [Advanced session recording](bpf-session-recording.mdx)
|
- [Advanced session recording](bpf-session-recording.mdx)
|
||||||
- [Restricting outbound network connections in SSH sessions](restricted-session.mdx)
|
- [Restricting outbound network connections in SSH sessions](restricted-session.mdx)
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,6 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/binary"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -95,6 +94,7 @@ import (
|
||||||
"github.com/gravitational/teleport/lib/service/servicecfg"
|
"github.com/gravitational/teleport/lib/service/servicecfg"
|
||||||
"github.com/gravitational/teleport/lib/services"
|
"github.com/gravitational/teleport/lib/services"
|
||||||
"github.com/gravitational/teleport/lib/session"
|
"github.com/gravitational/teleport/lib/session"
|
||||||
|
rsession "github.com/gravitational/teleport/lib/session"
|
||||||
"github.com/gravitational/teleport/lib/srv/alpnproxy/common"
|
"github.com/gravitational/teleport/lib/srv/alpnproxy/common"
|
||||||
"github.com/gravitational/teleport/lib/sshutils"
|
"github.com/gravitational/teleport/lib/sshutils"
|
||||||
"github.com/gravitational/teleport/lib/tlsca"
|
"github.com/gravitational/teleport/lib/tlsca"
|
||||||
|
@ -3779,14 +3779,23 @@ func testTrustedClusterAgentless(t *testing.T, suite *integrationTestSuite) {
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
tc, err := main.NewClientWithCreds(helpers.ClientConfig{
|
// create client for leaf cluster through root cluster
|
||||||
|
leafTC, err := main.NewClientWithCreds(helpers.ClientConfig{
|
||||||
Login: username,
|
Login: username,
|
||||||
Cluster: clusterAux,
|
Cluster: clusterAux,
|
||||||
Host: aux.InstanceListeners.ReverseTunnel,
|
Host: aux.InstanceListeners.ReverseTunnel,
|
||||||
}, *creds)
|
}, *creds)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
testAgentlessConn(t, tc, node)
|
// create client for root cluster
|
||||||
|
tc, err := main.NewClient(helpers.ClientConfig{
|
||||||
|
Login: suite.Me.Username,
|
||||||
|
Cluster: clusterMain,
|
||||||
|
Host: main.InstanceListeners.ReverseTunnel,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
testAgentlessConn(t, leafTC, tc, node)
|
||||||
|
|
||||||
// Stop clusters and remaining nodes.
|
// Stop clusters and remaining nodes.
|
||||||
require.NoError(t, main.StopAll())
|
require.NoError(t, main.StopAll())
|
||||||
|
@ -7866,8 +7875,8 @@ func testAgentlessConnection(t *testing.T, suite *integrationTestSuite) {
|
||||||
})
|
})
|
||||||
|
|
||||||
// get OpenSSH CA public key and create host certs
|
// get OpenSSH CA public key and create host certs
|
||||||
authClient := teleInst.Process.GetAuthServer()
|
authSrv := teleInst.Process.GetAuthServer()
|
||||||
node := createAgentlessNode(t, authClient, helpers.Site, "agentless-node")
|
node := createAgentlessNode(t, authSrv, helpers.Site, "agentless-node")
|
||||||
|
|
||||||
// create client
|
// create client
|
||||||
tc, err := teleInst.NewClient(helpers.ClientConfig{
|
tc, err := teleInst.NewClient(helpers.ClientConfig{
|
||||||
|
@ -7877,10 +7886,12 @@ func testAgentlessConnection(t *testing.T, suite *integrationTestSuite) {
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
testAgentlessConn(t, tc, node)
|
testAgentlessConn(t, tc, tc, node)
|
||||||
}
|
}
|
||||||
|
|
||||||
func createAgentlessNode(t *testing.T, authServer *auth.Server, clusterName, nodeHostname string) *types.ServerV2 {
|
func createAgentlessNode(t *testing.T, authServer *auth.Server, clusterName, nodeHostname string) *types.ServerV2 {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
openSSHCA, err := authServer.GetCertAuthority(ctx, types.CertAuthID{
|
openSSHCA, err := authServer.GetCertAuthority(ctx, types.CertAuthID{
|
||||||
Type: types.OpenSSHCA,
|
Type: types.OpenSSHCA,
|
||||||
|
@ -7965,6 +7976,8 @@ func createAgentlessNode(t *testing.T, authServer *auth.Server, clusterName, nod
|
||||||
// OpenSSH (agentless) server. The SSH server started only handles a small
|
// OpenSSH (agentless) server. The SSH server started only handles a small
|
||||||
// subset of SSH requests necessary for testing.
|
// subset of SSH requests necessary for testing.
|
||||||
func startSSHServer(t *testing.T, caPubKeys []ssh.PublicKey, hostKey ssh.Signer) string {
|
func startSSHServer(t *testing.T, caPubKeys []ssh.PublicKey, hostKey ssh.Signer) string {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
sshCfg := ssh.ServerConfig{
|
sshCfg := ssh.ServerConfig{
|
||||||
PublicKeyCallback: func(_ ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
|
PublicKeyCallback: func(_ ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
|
||||||
cert, ok := key.(*ssh.Certificate)
|
cert, ok := key.(*ssh.Certificate)
|
||||||
|
@ -8010,7 +8023,7 @@ func startSSHServer(t *testing.T, caPubKeys []ssh.PublicKey, hostKey ssh.Signer)
|
||||||
go ssh.DiscardRequests(reqs)
|
go ssh.DiscardRequests(reqs)
|
||||||
|
|
||||||
var agentForwarded bool
|
var agentForwarded bool
|
||||||
var cmdRequested bool
|
var shellRequested bool
|
||||||
for channelReq := range channels {
|
for channelReq := range channels {
|
||||||
assert.Equal(t, "session", channelReq.ChannelType())
|
assert.Equal(t, "session", channelReq.ChannelType())
|
||||||
channel, reqs, err := channelReq.Accept()
|
channel, reqs, err := channelReq.Accept()
|
||||||
|
@ -8026,25 +8039,24 @@ func startSSHServer(t *testing.T, caPubKeys []ssh.PublicKey, hostKey ssh.Signer)
|
||||||
}
|
}
|
||||||
if req.Type == sshutils.AgentForwardRequest {
|
if req.Type == sshutils.AgentForwardRequest {
|
||||||
agentForwarded = true
|
agentForwarded = true
|
||||||
} else if req.Type == sshutils.ExecRequest {
|
} else if req.Type == sshutils.ShellRequest {
|
||||||
_, err = channel.SendRequest("exit-status", false, binary.BigEndian.AppendUint32(nil, 0))
|
assert.NoError(t, channel.Close())
|
||||||
assert.NoError(t, err)
|
|
||||||
err = channel.Close()
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
cmdRequested = true
|
shellRequested = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
assert.True(t, agentForwarded)
|
assert.True(t, agentForwarded)
|
||||||
assert.True(t, cmdRequested)
|
assert.True(t, shellRequested)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return lis.Addr().String()
|
return lis.Addr().String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func testAgentlessConn(t *testing.T, tc *client.TeleportClient, node *types.ServerV2) {
|
func testAgentlessConn(t *testing.T, tc, joinTC *client.TeleportClient, node *types.ServerV2) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
// connect to cluster
|
// connect to cluster
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
clt, err := tc.ConnectToCluster(ctx)
|
clt, err := tc.ConnectToCluster(ctx)
|
||||||
|
@ -8053,6 +8065,16 @@ func testAgentlessConn(t *testing.T, tc *client.TeleportClient, node *types.Serv
|
||||||
require.NoError(t, clt.Close())
|
require.NoError(t, clt.Close())
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// connect to other cluster if needed
|
||||||
|
joinClt := clt
|
||||||
|
if tc != joinTC {
|
||||||
|
joinClt, err = joinTC.ConnectToCluster(ctx)
|
||||||
|
require.NoError(t, err)
|
||||||
|
t.Cleanup(func() {
|
||||||
|
require.NoError(t, joinClt.Close())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// connect to node
|
// connect to node
|
||||||
_, port, err := net.SplitHostPort(node.Spec.Addr)
|
_, port, err := net.SplitHostPort(node.Spec.Addr)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -8106,9 +8128,26 @@ func testAgentlessConn(t *testing.T, tc *client.TeleportClient, node *types.Serv
|
||||||
|
|
||||||
require.NoError(t, agent.RequestAgentForwarding(session))
|
require.NoError(t, agent.RequestAgentForwarding(session))
|
||||||
|
|
||||||
// run a command
|
// request a shell so Teleport starts tracking this session
|
||||||
err = session.Run("cmd")
|
session.Stderr = io.Discard
|
||||||
require.NoError(t, err)
|
session.Stdout = io.Discard
|
||||||
|
require.NoError(t, session.Shell())
|
||||||
|
|
||||||
|
var sessTracker types.SessionTracker
|
||||||
|
require.Eventually(t, func() bool {
|
||||||
|
trackers, err := joinClt.AuthClient.GetActiveSessionTrackers(ctx)
|
||||||
|
require.NoError(t, err)
|
||||||
|
if len(trackers) == 1 {
|
||||||
|
sessTracker = trackers[0]
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}, 3*time.Second, 100*time.Millisecond)
|
||||||
|
|
||||||
|
// test that attempting to join the session returns an error
|
||||||
|
err = joinTC.Join(ctx, types.SessionPeerMode, tc.Namespace, rsession.ID(sessTracker.GetSessionID()), nil)
|
||||||
|
require.True(t, trace.IsBadParameter(err))
|
||||||
|
require.ErrorContains(t, err, "session joining is only supported for Teleport nodes, not OpenSSH nodes")
|
||||||
|
|
||||||
// test that SSH agent channel is closed properly
|
// test that SSH agent channel is closed properly
|
||||||
select {
|
select {
|
||||||
|
|
|
@ -1918,6 +1918,9 @@ func (tc *TeleportClient) Join(ctx context.Context, mode types.SessionParticipan
|
||||||
if session.GetSessionKind() != types.SSHSessionKind {
|
if session.GetSessionKind() != types.SSHSessionKind {
|
||||||
return trace.BadParameter("session joining is only supported for ssh sessions, not %q sessions", session.GetSessionKind())
|
return trace.BadParameter("session joining is only supported for ssh sessions, not %q sessions", session.GetSessionKind())
|
||||||
}
|
}
|
||||||
|
if types.IsOpenSSHNodeSubKind(session.GetTargetSubKind()) {
|
||||||
|
return trace.BadParameter("session joining is only supported for Teleport nodes, not OpenSSH nodes")
|
||||||
|
}
|
||||||
|
|
||||||
// connect to server:
|
// connect to server:
|
||||||
nc, err := tc.ConnectToNode(ctx,
|
nc, err := tc.ConnectToNode(ctx,
|
||||||
|
|
|
@ -591,7 +591,7 @@ func (s *Server) Serve() {
|
||||||
|
|
||||||
if s.targetServer != nil && s.targetServer.IsOpenSSHNode() {
|
if s.targetServer != nil && s.targetServer.IsOpenSSHNode() {
|
||||||
// OpenSSH nodes don't support moderated sessions, send an error to
|
// OpenSSH nodes don't support moderated sessions, send an error to
|
||||||
// the user and gracefully fail the user is attempting to create one.
|
// the user and gracefully fail if the user is attempting to create one.
|
||||||
policySets := s.identityContext.AccessChecker.SessionPolicySets()
|
policySets := s.identityContext.AccessChecker.SessionPolicySets()
|
||||||
evaluator := auth.NewSessionAccessEvaluator(policySets, types.SSHSessionKind, s.identityContext.TeleportUser)
|
evaluator := auth.NewSessionAccessEvaluator(policySets, types.SSHSessionKind, s.identityContext.TeleportUser)
|
||||||
if evaluator.IsModerated() {
|
if evaluator.IsModerated() {
|
||||||
|
|
|
@ -1981,7 +1981,8 @@ func (s *session) trackSession(ctx context.Context, teleportUser string, policyS
|
||||||
LastActive: s.registry.clock.Now().UTC(),
|
LastActive: s.registry.clock.Now().UTC(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
HostID: s.registry.Srv.ID(),
|
HostID: s.registry.Srv.ID(),
|
||||||
|
TargetSubKind: s.serverMeta.ServerSubKind,
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.scx.env[teleport.EnvSSHSessionInvited] != "" {
|
if s.scx.env[teleport.EnvSSHSessionInvited] != "" {
|
||||||
|
|
Loading…
Reference in a new issue