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 join \<regular-node\>
|
||||
- [ ] tsh join \<node-remote-cluster\>
|
||||
- [ ] tsh join \<agentless-node\>
|
||||
- [ ] tsh join \<agentless-node-remote-cluster\>
|
||||
- [ ] tsh play \<regular-node\>
|
||||
- [ ] tsh play \<node-remote-cluster\>
|
||||
- [ ] tsh play \<agentless-node\>
|
||||
|
|
|
@ -4917,6 +4917,9 @@ message SessionTrackerSpecV1 {
|
|||
// 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.
|
||||
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
|
||||
|
|
|
@ -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() time.Time
|
||||
|
||||
// HostID is the target host id that created the session tracker.
|
||||
GetHostID() string
|
||||
|
||||
// GetTargetSubKind returns the sub kind of the target server.
|
||||
GetTargetSubKind() string
|
||||
}
|
||||
|
||||
func NewSessionTracker(spec SessionTrackerSpecV1) (SessionTracker, error) {
|
||||
|
@ -335,6 +339,11 @@ func (s *SessionTrackerV1) GetLastActive() time.Time {
|
|||
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.
|
||||
func (f *SessionTrackerFilter) Match(s SessionTracker) bool {
|
||||
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)
|
||||
- [Session recording without SSH connection termination](recording-proxy-mode.mdx)
|
||||
- [Session sharing](../../connect-your-client/tsh.mdx)
|
||||
- [Advanced session recording](bpf-session-recording.mdx)
|
||||
- [Restricting outbound network connections in SSH sessions](restricted-session.mdx)
|
||||
|
||||
|
|
|
@ -21,7 +21,6 @@ import (
|
|||
"bytes"
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
@ -95,6 +94,7 @@ import (
|
|||
"github.com/gravitational/teleport/lib/service/servicecfg"
|
||||
"github.com/gravitational/teleport/lib/services"
|
||||
"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/sshutils"
|
||||
"github.com/gravitational/teleport/lib/tlsca"
|
||||
|
@ -3779,14 +3779,23 @@ func testTrustedClusterAgentless(t *testing.T, suite *integrationTestSuite) {
|
|||
})
|
||||
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,
|
||||
Cluster: clusterAux,
|
||||
Host: aux.InstanceListeners.ReverseTunnel,
|
||||
}, *creds)
|
||||
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.
|
||||
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
|
||||
authClient := teleInst.Process.GetAuthServer()
|
||||
node := createAgentlessNode(t, authClient, helpers.Site, "agentless-node")
|
||||
authSrv := teleInst.Process.GetAuthServer()
|
||||
node := createAgentlessNode(t, authSrv, helpers.Site, "agentless-node")
|
||||
|
||||
// create client
|
||||
tc, err := teleInst.NewClient(helpers.ClientConfig{
|
||||
|
@ -7877,10 +7886,12 @@ func testAgentlessConnection(t *testing.T, suite *integrationTestSuite) {
|
|||
})
|
||||
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 {
|
||||
t.Helper()
|
||||
|
||||
ctx := context.Background()
|
||||
openSSHCA, err := authServer.GetCertAuthority(ctx, types.CertAuthID{
|
||||
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
|
||||
// subset of SSH requests necessary for testing.
|
||||
func startSSHServer(t *testing.T, caPubKeys []ssh.PublicKey, hostKey ssh.Signer) string {
|
||||
t.Helper()
|
||||
|
||||
sshCfg := ssh.ServerConfig{
|
||||
PublicKeyCallback: func(_ ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
|
||||
cert, ok := key.(*ssh.Certificate)
|
||||
|
@ -8010,7 +8023,7 @@ func startSSHServer(t *testing.T, caPubKeys []ssh.PublicKey, hostKey ssh.Signer)
|
|||
go ssh.DiscardRequests(reqs)
|
||||
|
||||
var agentForwarded bool
|
||||
var cmdRequested bool
|
||||
var shellRequested bool
|
||||
for channelReq := range channels {
|
||||
assert.Equal(t, "session", channelReq.ChannelType())
|
||||
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 {
|
||||
agentForwarded = true
|
||||
} else if req.Type == sshutils.ExecRequest {
|
||||
_, err = channel.SendRequest("exit-status", false, binary.BigEndian.AppendUint32(nil, 0))
|
||||
assert.NoError(t, err)
|
||||
err = channel.Close()
|
||||
assert.NoError(t, err)
|
||||
} else if req.Type == sshutils.ShellRequest {
|
||||
assert.NoError(t, channel.Close())
|
||||
|
||||
cmdRequested = true
|
||||
shellRequested = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
assert.True(t, agentForwarded)
|
||||
assert.True(t, cmdRequested)
|
||||
assert.True(t, shellRequested)
|
||||
}()
|
||||
|
||||
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
|
||||
ctx := context.Background()
|
||||
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())
|
||||
})
|
||||
|
||||
// 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
|
||||
_, port, err := net.SplitHostPort(node.Spec.Addr)
|
||||
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))
|
||||
|
||||
// run a command
|
||||
err = session.Run("cmd")
|
||||
require.NoError(t, err)
|
||||
// request a shell so Teleport starts tracking this session
|
||||
session.Stderr = io.Discard
|
||||
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
|
||||
select {
|
||||
|
|
|
@ -1918,6 +1918,9 @@ func (tc *TeleportClient) Join(ctx context.Context, mode types.SessionParticipan
|
|||
if session.GetSessionKind() != types.SSHSessionKind {
|
||||
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:
|
||||
nc, err := tc.ConnectToNode(ctx,
|
||||
|
|
|
@ -591,7 +591,7 @@ func (s *Server) Serve() {
|
|||
|
||||
if s.targetServer != nil && s.targetServer.IsOpenSSHNode() {
|
||||
// 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()
|
||||
evaluator := auth.NewSessionAccessEvaluator(policySets, types.SSHSessionKind, s.identityContext.TeleportUser)
|
||||
if evaluator.IsModerated() {
|
||||
|
|
|
@ -1981,7 +1981,8 @@ func (s *session) trackSession(ctx context.Context, teleportUser string, policyS
|
|||
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] != "" {
|
||||
|
|
Loading…
Reference in a new issue