mirror of
https://github.com/gravitational/teleport.git
synced 2026-09-24 16:17:11 +08:00
Remove legacy session service (#15155)
This commit is contained in:
+1
-1
Submodule e updated: fbb63ffef1...393cd15422
@@ -53,6 +53,7 @@ import (
|
||||
"github.com/gravitational/teleport/api/client/proto"
|
||||
"github.com/gravitational/teleport/api/constants"
|
||||
"github.com/gravitational/teleport/api/defaults"
|
||||
apidefaults "github.com/gravitational/teleport/api/defaults"
|
||||
tracessh "github.com/gravitational/teleport/api/observability/tracing/ssh"
|
||||
"github.com/gravitational/teleport/api/profile"
|
||||
"github.com/gravitational/teleport/api/types"
|
||||
@@ -384,7 +385,7 @@ func testAuditOn(t *testing.T, suite *integrationTestSuite) {
|
||||
require.NoError(t, err)
|
||||
|
||||
// should have no sessions:
|
||||
sessions, err := site.GetSessions(ctx, defaults.Namespace)
|
||||
sessions, err := site.GetActiveSessionTrackers(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, sessions)
|
||||
|
||||
@@ -408,27 +409,27 @@ func testAuditOn(t *testing.T, suite *integrationTestSuite) {
|
||||
}()
|
||||
|
||||
// wait until we've found the session in the audit log
|
||||
getSession := func(site auth.ClientI) (*session.Session, error) {
|
||||
getSession := func(site auth.ClientI) (types.SessionTracker, error) {
|
||||
timeout, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
sessions, err := waitForSessionToBeEstablished(timeout, defaults.Namespace, site)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
return &sessions[0], nil
|
||||
return sessions[0], nil
|
||||
}
|
||||
session, err := getSession(site)
|
||||
tracker, err := getSession(site)
|
||||
require.NoError(t, err)
|
||||
sessionID := session.ID
|
||||
sessionID := tracker.GetSessionID()
|
||||
|
||||
// wait for the user to join this session:
|
||||
for len(session.Parties) == 0 {
|
||||
for len(tracker.GetParticipants()) == 0 {
|
||||
time.Sleep(time.Millisecond * 5)
|
||||
session, err = site.GetSession(ctx, defaults.Namespace, sessionID)
|
||||
tracker, err = site.GetSessionTracker(ctx, tracker.GetSessionID())
|
||||
require.NoError(t, err)
|
||||
}
|
||||
// make sure it's us who joined! :)
|
||||
require.Equal(t, suite.Me.Username, session.Parties[0].User)
|
||||
require.Equal(t, suite.Me.Username, tracker.GetParticipants()[0].User)
|
||||
|
||||
// lets type "echo hi" followed by "enter" and then "exit" + "enter":
|
||||
|
||||
@@ -447,15 +448,15 @@ func testAuditOn(t *testing.T, suite *integrationTestSuite) {
|
||||
for {
|
||||
select {
|
||||
case event := <-teleport.UploadEventsC:
|
||||
if event.SessionID != string(session.ID) {
|
||||
t.Logf("Skipping mismatching session %v, expecting upload of %v.", event.SessionID, session.ID)
|
||||
if event.SessionID != tracker.GetSessionID() {
|
||||
t.Logf("Skipping mismatching session %v, expecting upload of %v.", event.SessionID, tracker.GetSessionID())
|
||||
continue
|
||||
}
|
||||
break loop
|
||||
case <-timeoutC:
|
||||
dumpGoroutineProfile()
|
||||
t.Fatalf("%s: Timeout waiting for upload of session %v to complete to %v",
|
||||
tt.comment, session.ID, tt.auditSessionsURI)
|
||||
tt.comment, tracker.GetSessionID(), tt.auditSessionsURI)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -463,7 +464,7 @@ func testAuditOn(t *testing.T, suite *integrationTestSuite) {
|
||||
// everything because the session is closing)
|
||||
var sessionStream []byte
|
||||
for i := 0; i < 6; i++ {
|
||||
sessionStream, err = site.GetSessionChunk(defaults.Namespace, session.ID, 0, events.MaxChunkBytes)
|
||||
sessionStream, err = site.GetSessionChunk(apidefaults.Namespace, session.ID(tracker.GetSessionID()), 0, events.MaxChunkBytes)
|
||||
require.NoError(t, err)
|
||||
if strings.Contains(string(sessionStream), "exit") {
|
||||
break
|
||||
@@ -495,7 +496,7 @@ func testAuditOn(t *testing.T, suite *integrationTestSuite) {
|
||||
select {
|
||||
case <-tickCh:
|
||||
// Get all session events from the backend.
|
||||
sessionEvents, err := site.GetSessionEvents(defaults.Namespace, session.ID, 0, false)
|
||||
sessionEvents, err := site.GetSessionEvents(apidefaults.Namespace, session.ID(tracker.GetSessionID()), 0, false)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
@@ -554,7 +555,7 @@ func testAuditOn(t *testing.T, suite *integrationTestSuite) {
|
||||
start := findByType(events.SessionStartEvent)
|
||||
require.Equal(t, first, start)
|
||||
require.Equal(t, 0, start.GetInt("bytes"))
|
||||
require.Equal(t, string(sessionID), start.GetString(events.SessionEventID))
|
||||
require.Equal(t, sessionID, start.GetString(events.SessionEventID))
|
||||
require.NotEmpty(t, start.GetString(events.TerminalSize))
|
||||
|
||||
// make sure data is recorded properly
|
||||
@@ -570,13 +571,13 @@ func testAuditOn(t *testing.T, suite *integrationTestSuite) {
|
||||
end := findByType(events.SessionEndEvent)
|
||||
require.NotNil(t, end)
|
||||
require.Equal(t, 0, end.GetInt("bytes"))
|
||||
require.Equal(t, string(sessionID), end.GetString(events.SessionEventID))
|
||||
require.Equal(t, sessionID, end.GetString(events.SessionEventID))
|
||||
|
||||
// there should always be 'session.leave' event
|
||||
leave := findByType(events.SessionLeaveEvent)
|
||||
require.NotNil(t, leave)
|
||||
require.Equal(t, 0, leave.GetInt("bytes"))
|
||||
require.Equal(t, string(sessionID), leave.GetString(events.SessionEventID))
|
||||
require.Equal(t, sessionID, leave.GetString(events.SessionEventID))
|
||||
|
||||
// all of them should have a proper time
|
||||
for _, e := range history {
|
||||
@@ -1256,7 +1257,7 @@ func verifySessionJoin(t *testing.T, username string, teleport *helpers.TeleInst
|
||||
return
|
||||
}
|
||||
|
||||
sessionID := string(sessions[0].ID)
|
||||
sessionID := sessions[0].GetSessionID()
|
||||
cl, err := teleport.NewClient(helpers.ClientConfig{
|
||||
Login: username,
|
||||
Cluster: helpers.Site,
|
||||
@@ -3811,7 +3812,7 @@ func testAuditOff(t *testing.T, suite *integrationTestSuite) {
|
||||
require.NotNil(t, site)
|
||||
|
||||
// should have no sessions in it to start with
|
||||
sessions, _ := site.GetSessions(ctx, defaults.Namespace)
|
||||
sessions, _ := site.GetActiveSessionTrackers(ctx)
|
||||
require.Len(t, sessions, 0)
|
||||
|
||||
// create interactive session (this goroutine is this user's terminal time)
|
||||
@@ -3837,16 +3838,16 @@ func testAuditOff(t *testing.T, suite *integrationTestSuite) {
|
||||
defer cancel()
|
||||
sessions, err = waitForSessionToBeEstablished(timeoutCtx, defaults.Namespace, site)
|
||||
require.NoError(t, err)
|
||||
session := &sessions[0]
|
||||
tracker := sessions[0]
|
||||
|
||||
// wait for the user to join this session
|
||||
for len(session.Parties) == 0 {
|
||||
for len(tracker.GetParticipants()) == 0 {
|
||||
time.Sleep(time.Millisecond * 5)
|
||||
session, err = site.GetSession(ctx, defaults.Namespace, sessions[0].ID)
|
||||
tracker, err = site.GetSessionTracker(ctx, sessions[0].GetSessionID())
|
||||
require.NoError(t, err)
|
||||
}
|
||||
// make sure it's us who joined! :)
|
||||
require.Equal(t, suite.Me.Username, session.Parties[0].User)
|
||||
require.Equal(t, suite.Me.Username, tracker.GetParticipants()[0].User)
|
||||
|
||||
// lets type "echo hi" followed by "enter" and then "exit" + "enter":
|
||||
myTerm.Type("\aecho hi\n\r\aexit\n\r\a")
|
||||
@@ -3863,7 +3864,7 @@ func testAuditOff(t *testing.T, suite *integrationTestSuite) {
|
||||
|
||||
// however, attempts to read the actual sessions should fail because it was
|
||||
// not actually recorded
|
||||
_, err = site.GetSessionChunk(defaults.Namespace, session.ID, 0, events.MaxChunkBytes)
|
||||
_, err = site.GetSessionChunk(apidefaults.Namespace, session.ID(tracker.GetSessionID()), 0, events.MaxChunkBytes)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
@@ -4697,7 +4698,7 @@ func testWindowChange(t *testing.T, suite *integrationTestSuite) {
|
||||
defer cancel()
|
||||
sessions, err := waitForSessionToBeEstablished(timeoutCtx, defaults.Namespace, site)
|
||||
require.NoError(t, err)
|
||||
sessionID := string(sessions[0].ID)
|
||||
sessionID := sessions[0].GetSessionID()
|
||||
|
||||
cl, err := teleport.NewClient(helpers.ClientConfig{
|
||||
Login: suite.Me.Username,
|
||||
|
||||
@@ -31,7 +31,6 @@ import (
|
||||
"github.com/gravitational/teleport/integration/helpers"
|
||||
"github.com/gravitational/teleport/lib/auth"
|
||||
"github.com/gravitational/teleport/lib/client"
|
||||
"github.com/gravitational/teleport/lib/session"
|
||||
|
||||
"github.com/gravitational/trace"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -49,7 +48,7 @@ func extractPort(svr *httptest.Server) (int, error) {
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func waitForSessionToBeEstablished(ctx context.Context, namespace string, site auth.ClientI) ([]session.Session, error) {
|
||||
func waitForSessionToBeEstablished(ctx context.Context, namespace string, site auth.ClientI) ([]types.SessionTracker, error) {
|
||||
|
||||
ticker := time.NewTicker(100 * time.Millisecond)
|
||||
defer ticker.Stop()
|
||||
@@ -60,7 +59,7 @@ func waitForSessionToBeEstablished(ctx context.Context, namespace string, site a
|
||||
return nil, ctx.Err()
|
||||
|
||||
case <-ticker.C:
|
||||
ss, err := site.GetSessions(ctx, namespace)
|
||||
ss, err := site.GetActiveSessionTrackers(ctx)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
@@ -268,7 +268,6 @@ func newSrvCtx(ctx context.Context, t *testing.T) *SrvCtx {
|
||||
regular.SetNamespace(apidefaults.Namespace),
|
||||
regular.SetEmitter(s.nodeClient),
|
||||
regular.SetShell("/bin/sh"),
|
||||
regular.SetSessionServer(s.nodeClient),
|
||||
regular.SetPAMConfig(&pam.Config{Enabled: false}),
|
||||
regular.SetLabels(
|
||||
map[string]string{"foo": "bar"},
|
||||
|
||||
@@ -45,7 +45,6 @@ import (
|
||||
type APIConfig struct {
|
||||
PluginRegistry plugin.Registry
|
||||
AuthServer *Server
|
||||
SessionService session.Service
|
||||
AuditLog events.IAuditLog
|
||||
Authorizer Authorizer
|
||||
Emitter apievents.Emitter
|
||||
@@ -154,11 +153,6 @@ func NewAPIServer(config *APIConfig) (http.Handler, error) {
|
||||
srv.POST("/:version/tokens/register", srv.withAuth(srv.registerUsingToken))
|
||||
|
||||
// Active sessions
|
||||
srv.POST("/:version/namespaces/:namespace/sessions", srv.withAuth(srv.createSession))
|
||||
srv.PUT("/:version/namespaces/:namespace/sessions/:id", srv.withAuth(srv.updateSession))
|
||||
srv.DELETE("/:version/namespaces/:namespace/sessions/:id", srv.withAuth(srv.deleteSession))
|
||||
srv.GET("/:version/namespaces/:namespace/sessions", srv.withAuth(srv.getSessions))
|
||||
srv.GET("/:version/namespaces/:namespace/sessions/:id", srv.withAuth(srv.getSession))
|
||||
srv.GET("/:version/namespaces/:namespace/sessions/:id/stream", srv.withAuth(srv.getSessionChunk))
|
||||
srv.GET("/:version/namespaces/:namespace/sessions/:id/events", srv.withAuth(srv.getSessionEvents))
|
||||
|
||||
@@ -229,7 +223,6 @@ func (s *APIServer) withAuth(handler HandlerWithAuthFunc) httprouter.Handle {
|
||||
auth := &ServerWithRoles{
|
||||
authServer: s.AuthServer,
|
||||
context: *authContext,
|
||||
sessions: s.SessionService,
|
||||
alog: s.AuthServer,
|
||||
}
|
||||
version := p.ByName("version")
|
||||
@@ -867,82 +860,6 @@ func (s *APIServer) deleteCertAuthority(auth ClientI, w http.ResponseWriter, r *
|
||||
return message(fmt.Sprintf("cert '%v' deleted", id)), nil
|
||||
}
|
||||
|
||||
type createSessionReq struct {
|
||||
Session session.Session `json:"session"`
|
||||
}
|
||||
|
||||
func (s *APIServer) createSession(auth ClientI, w http.ResponseWriter, r *http.Request, p httprouter.Params, version string) (interface{}, error) {
|
||||
var req *createSessionReq
|
||||
if err := httplib.ReadJSON(r, &req); err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
namespace := p.ByName("namespace")
|
||||
if !types.IsValidNamespace(namespace) {
|
||||
return nil, trace.BadParameter("invalid namespace %q", namespace)
|
||||
}
|
||||
req.Session.Namespace = namespace
|
||||
if err := auth.CreateSession(r.Context(), req.Session); err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
return message("ok"), nil
|
||||
}
|
||||
|
||||
type updateSessionReq struct {
|
||||
Update session.UpdateRequest `json:"update"`
|
||||
}
|
||||
|
||||
func (s *APIServer) updateSession(auth ClientI, w http.ResponseWriter, r *http.Request, p httprouter.Params, version string) (interface{}, error) {
|
||||
var req *updateSessionReq
|
||||
if err := httplib.ReadJSON(r, &req); err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
namespace := p.ByName("namespace")
|
||||
if !types.IsValidNamespace(namespace) {
|
||||
return nil, trace.BadParameter("invalid namespace %q", namespace)
|
||||
}
|
||||
req.Update.Namespace = namespace
|
||||
if err := auth.UpdateSession(r.Context(), req.Update); err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
return message("ok"), nil
|
||||
}
|
||||
|
||||
func (s *APIServer) deleteSession(auth ClientI, w http.ResponseWriter, r *http.Request, p httprouter.Params, version string) (interface{}, error) {
|
||||
err := auth.DeleteSession(r.Context(), p.ByName("namespace"), session.ID(p.ByName("id")))
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
return message("ok"), nil
|
||||
}
|
||||
|
||||
func (s *APIServer) getSessions(auth ClientI, w http.ResponseWriter, r *http.Request, p httprouter.Params, version string) (interface{}, error) {
|
||||
namespace := p.ByName("namespace")
|
||||
if !types.IsValidNamespace(namespace) {
|
||||
return nil, trace.BadParameter("invalid namespace %q", namespace)
|
||||
}
|
||||
sessions, err := auth.GetSessions(r.Context(), namespace)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
return sessions, nil
|
||||
}
|
||||
|
||||
func (s *APIServer) getSession(auth ClientI, w http.ResponseWriter, r *http.Request, p httprouter.Params, version string) (interface{}, error) {
|
||||
sid, err := session.ParseID(p.ByName("id"))
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
namespace := p.ByName("namespace")
|
||||
if !types.IsValidNamespace(namespace) {
|
||||
return nil, trace.BadParameter("invalid namespace %q", namespace)
|
||||
}
|
||||
se, err := auth.GetSession(r.Context(), namespace, *sid)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
return se, nil
|
||||
}
|
||||
|
||||
type createOIDCAuthRequestReq struct {
|
||||
Req types.OIDCAuthRequest `json:"req"`
|
||||
}
|
||||
|
||||
@@ -1,244 +0,0 @@
|
||||
// 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 auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sort"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
apidefaults "github.com/gravitational/teleport/api/defaults"
|
||||
"github.com/gravitational/teleport/api/types"
|
||||
"github.com/gravitational/teleport/lib/services"
|
||||
"github.com/gravitational/teleport/lib/session"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/gravitational/trace"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestAPIServer_activeSessions_whereConditions(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := context.Background()
|
||||
tlsServer := newTestTLSServer(t)
|
||||
authServer := tlsServer.Auth()
|
||||
|
||||
// - "admin" has permissions to access all active sessions
|
||||
// - "alpaca" has permissions to access only their own active sessions
|
||||
// Each user is assigned its corresponding role, plus whatever extra
|
||||
// permissions are needed to run the scenario.
|
||||
const admin = "admin"
|
||||
const alpaca = "alpaca"
|
||||
alpacaRole := services.RoleForUser(&types.UserV2{Metadata: types.Metadata{Name: alpaca}})
|
||||
alpacaRole.SetLogins(types.Allow, []string{alpaca})
|
||||
alpacaRole.SetRules(types.Allow, append(alpacaRole.GetRules(types.Allow), types.Rule{
|
||||
Resources: []string{"ssh_session"},
|
||||
// Allow all ssh_session verbs, deny rule below takes precedence.
|
||||
Verbs: []string{"*"},
|
||||
}))
|
||||
alpacaRole.SetRules(types.Deny, append(alpacaRole.GetRules(types.Deny), types.Rule{
|
||||
Resources: []string{"ssh_session"},
|
||||
Verbs: []string{"list", "read", "update", "delete"},
|
||||
Where: "!contains(ssh_session.participants, user.metadata.name)",
|
||||
}))
|
||||
_, err := CreateUser(authServer, alpaca, alpacaRole)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Prepare clients.
|
||||
adminClient, err := tlsServer.NewClient(TestAdmin())
|
||||
require.NoError(t, err)
|
||||
alpacaClient, err := tlsServer.NewClient(TestUser(alpaca))
|
||||
require.NoError(t, err)
|
||||
|
||||
// Prepare one session per user.
|
||||
createSession := func(clt ClientI, user string) session.ID {
|
||||
id := session.NewID()
|
||||
now := time.Now()
|
||||
|
||||
// Create initial session.
|
||||
require.NoError(t, clt.CreateSession(ctx, session.Session{
|
||||
ID: id,
|
||||
Namespace: apidefaults.Namespace,
|
||||
TerminalParams: session.TerminalParams{
|
||||
W: 100,
|
||||
H: 100,
|
||||
},
|
||||
Login: user,
|
||||
Created: now,
|
||||
LastActive: now,
|
||||
}))
|
||||
|
||||
// Add parties, must be done via update.
|
||||
// Usually the Node does this, in the test we are taking a shortcut and
|
||||
// using admin due to its powerful permissions.
|
||||
require.NoError(t, adminClient.UpdateSession(ctx, session.UpdateRequest{
|
||||
ID: id,
|
||||
Namespace: apidefaults.Namespace,
|
||||
Parties: &[]session.Party{
|
||||
{ID: session.NewID(), User: user},
|
||||
},
|
||||
}))
|
||||
return id
|
||||
}
|
||||
adminSessionID := createSession(adminClient, admin)
|
||||
alpacaSessionID := createSession(alpacaClient, alpaca)
|
||||
|
||||
t.Run("GetSessions respects role conditions", func(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
clt ClientI
|
||||
wantIDs []session.ID
|
||||
}{
|
||||
{
|
||||
name: admin,
|
||||
clt: adminClient,
|
||||
wantIDs: []session.ID{adminSessionID, alpacaSessionID},
|
||||
},
|
||||
{
|
||||
name: alpaca,
|
||||
clt: alpacaClient,
|
||||
wantIDs: []session.ID{alpacaSessionID},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
sessions, err := test.clt.GetSessions(ctx, apidefaults.Namespace)
|
||||
require.NoError(t, err)
|
||||
|
||||
got := make([]session.ID, len(sessions))
|
||||
for i, s := range sessions {
|
||||
got[i] = s.ID
|
||||
}
|
||||
want := test.wantIDs
|
||||
sort.Slice(got, func(i, j int) bool { return got[i] < got[j] })
|
||||
sort.Slice(want, func(i, j int) bool { return want[i] < want[j] })
|
||||
if diff := cmp.Diff(test.wantIDs, got); diff != "" {
|
||||
t.Errorf("GetSessions() mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// Helper functions used by test cases below.
|
||||
getSession := func(clt ClientI) func(id session.ID) error {
|
||||
return func(id session.ID) error {
|
||||
_, err := clt.GetSession(ctx, apidefaults.Namespace, id)
|
||||
return err
|
||||
}
|
||||
}
|
||||
updateSession := func(clt ClientI) func(id session.ID) error {
|
||||
return func(id session.ID) error {
|
||||
return clt.UpdateSession(ctx, session.UpdateRequest{
|
||||
ID: id,
|
||||
Namespace: apidefaults.Namespace,
|
||||
TerminalParams: &session.TerminalParams{W: 150, H: 150},
|
||||
})
|
||||
}
|
||||
}
|
||||
deleteSession := func(clt ClientI) func(id session.ID) error {
|
||||
return func(id session.ID) error {
|
||||
return clt.UpdateSession(ctx, session.UpdateRequest{
|
||||
ID: id,
|
||||
Namespace: apidefaults.Namespace,
|
||||
TerminalParams: &session.TerminalParams{W: 150, H: 150},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
t.Run("users can't interact with denied sessions", func(t *testing.T) {
|
||||
clt := alpacaClient
|
||||
sessionID := adminSessionID
|
||||
tests := []struct {
|
||||
name string
|
||||
fn func(id session.ID) error
|
||||
}{
|
||||
{
|
||||
name: "GetSession",
|
||||
fn: getSession(clt),
|
||||
},
|
||||
{
|
||||
name: "UpdateSession",
|
||||
fn: updateSession(clt),
|
||||
},
|
||||
{
|
||||
name: "DeleteSession",
|
||||
fn: deleteSession(clt),
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
err := test.fn(sessionID)
|
||||
require.True(t, trace.IsAccessDenied(err), "unexpected err: %v (want access denied)", err)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("users can interact with allowed sessions", func(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
fn func(session.ID) error
|
||||
sessionID session.ID
|
||||
}{
|
||||
{
|
||||
name: "admin reads own session",
|
||||
fn: getSession(adminClient),
|
||||
sessionID: adminSessionID,
|
||||
},
|
||||
{
|
||||
name: "admin updates own session",
|
||||
fn: updateSession(adminClient),
|
||||
sessionID: adminSessionID,
|
||||
},
|
||||
{
|
||||
name: "admin deletes own session",
|
||||
fn: deleteSession(adminClient),
|
||||
sessionID: adminSessionID,
|
||||
},
|
||||
{
|
||||
name: "admin reads alpaca session",
|
||||
fn: getSession(adminClient),
|
||||
sessionID: alpacaSessionID,
|
||||
},
|
||||
{
|
||||
name: "admin updates alpaca session",
|
||||
fn: updateSession(adminClient),
|
||||
sessionID: alpacaSessionID,
|
||||
},
|
||||
|
||||
{
|
||||
name: "alpaca reads own session",
|
||||
fn: getSession(alpacaClient),
|
||||
sessionID: alpacaSessionID,
|
||||
},
|
||||
{
|
||||
name: "alpaca updates own session",
|
||||
fn: updateSession(alpacaClient),
|
||||
sessionID: alpacaSessionID,
|
||||
},
|
||||
{
|
||||
name: "alpaca deletes own session",
|
||||
fn: deleteSession(alpacaClient),
|
||||
sessionID: alpacaSessionID,
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
require.NoError(t, test.fn(test.sessionID))
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -52,7 +52,6 @@ import (
|
||||
// methods that focuses on authorizing every request
|
||||
type ServerWithRoles struct {
|
||||
authServer *Server
|
||||
sessions session.Service
|
||||
alog events.IAuditLog
|
||||
// context holds authorization context
|
||||
context Context
|
||||
@@ -186,18 +185,6 @@ func (a *ServerWithRoles) actionForKindSession(namespace, verb string, sid sessi
|
||||
return trace.Wrap(a.actionWithExtendedContext(namespace, types.KindSession, verb, extendContext))
|
||||
}
|
||||
|
||||
// actionForKindSSHSession is a special checker that grants access to active SSH
|
||||
// sessions. It can allow access to a specific session based on the `where`
|
||||
// section of the user's access rule for kind `ssh_session`.
|
||||
func (a *ServerWithRoles) actionForKindSSHSession(ctx context.Context, namespace, verb string, sid session.ID) error {
|
||||
extendContext := func(serviceContext *services.Context) error {
|
||||
session, err := a.sessions.GetSession(ctx, namespace, sid)
|
||||
serviceContext.SSHSession = session
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
return trace.Wrap(a.actionWithExtendedContext(namespace, types.KindSSHSession, verb, extendContext))
|
||||
}
|
||||
|
||||
// serverAction returns an access denied error if the role is not one of the builtin server roles.
|
||||
func (a *ServerWithRoles) serverAction() error {
|
||||
role, ok := a.context.Identity.(BuiltinRole)
|
||||
@@ -505,62 +492,6 @@ func (a *ServerWithRoles) AuthenticateSSHUser(ctx context.Context, req Authentic
|
||||
return a.authServer.AuthenticateSSHUser(ctx, req)
|
||||
}
|
||||
|
||||
func (a *ServerWithRoles) GetSessions(ctx context.Context, namespace string) ([]session.Session, error) {
|
||||
cond, err := a.actionForListWithCondition(namespace, types.KindSSHSession, services.SSHSessionIdentifier)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
sessions, err := a.sessions.GetSessions(ctx, namespace)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
if cond == nil {
|
||||
return sessions, nil
|
||||
}
|
||||
|
||||
// Filter sessions according to cond.
|
||||
filteredSessions := make([]session.Session, 0, len(sessions))
|
||||
ruleCtx := &services.Context{User: a.context.User}
|
||||
for _, s := range sessions {
|
||||
ruleCtx.SSHSession = &s
|
||||
if err := a.context.Checker.CheckAccessToRule(ruleCtx, namespace, types.KindSSHSession, types.VerbList, true /* silent */); err != nil {
|
||||
continue
|
||||
}
|
||||
filteredSessions = append(filteredSessions, s)
|
||||
}
|
||||
return filteredSessions, nil
|
||||
}
|
||||
|
||||
func (a *ServerWithRoles) GetSession(ctx context.Context, namespace string, id session.ID) (*session.Session, error) {
|
||||
if err := a.actionForKindSSHSession(ctx, namespace, types.VerbRead, id); err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
return a.sessions.GetSession(ctx, namespace, id)
|
||||
}
|
||||
|
||||
func (a *ServerWithRoles) CreateSession(ctx context.Context, s session.Session) error {
|
||||
if err := a.action(s.Namespace, types.KindSSHSession, types.VerbCreate); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
return a.sessions.CreateSession(ctx, s)
|
||||
}
|
||||
|
||||
func (a *ServerWithRoles) UpdateSession(ctx context.Context, req session.UpdateRequest) error {
|
||||
if err := a.actionForKindSSHSession(ctx, req.Namespace, types.VerbUpdate, req.ID); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
return a.sessions.UpdateSession(ctx, req)
|
||||
}
|
||||
|
||||
// DeleteSession removes an active session from the backend.
|
||||
func (a *ServerWithRoles) DeleteSession(ctx context.Context, namespace string, id session.ID) error {
|
||||
if err := a.actionForKindSSHSession(ctx, namespace, types.VerbDelete, id); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
return a.sessions.DeleteSession(ctx, namespace, id)
|
||||
}
|
||||
|
||||
// CreateCertAuthority not implemented: can only be called locally.
|
||||
func (a *ServerWithRoles) CreateCertAuthority(ca types.CertAuthority) error {
|
||||
return trace.NotImplemented(notImplementedMessage)
|
||||
@@ -5035,7 +4966,7 @@ func (a *ServerWithRoles) MaintainSessionPresence(ctx context.Context) (proto.Au
|
||||
|
||||
// NewAdminAuthServer returns auth server authorized as admin,
|
||||
// used for auth server cached access
|
||||
func NewAdminAuthServer(authServer *Server, sessions session.Service, alog events.IAuditLog) (ClientI, error) {
|
||||
func NewAdminAuthServer(authServer *Server, alog events.IAuditLog) (ClientI, error) {
|
||||
ctx, err := NewAdminContext()
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
@@ -5044,7 +4975,6 @@ func NewAdminAuthServer(authServer *Server, sessions session.Service, alog event
|
||||
authServer: authServer,
|
||||
context: *ctx,
|
||||
alog: alog,
|
||||
sessions: sessions,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -1474,7 +1474,6 @@ func serverWithAllowRules(t *testing.T, srv *TestAuthServer, allowRules []types.
|
||||
|
||||
return &ServerWithRoles{
|
||||
authServer: srv.AuthServer,
|
||||
sessions: srv.SessionServer,
|
||||
alog: srv.AuditLog,
|
||||
context: *authContext,
|
||||
}
|
||||
@@ -2137,7 +2136,6 @@ func TestReplaceRemoteLocksRBAC(t *testing.T) {
|
||||
|
||||
s := &ServerWithRoles{
|
||||
authServer: srv.AuthServer,
|
||||
sessions: srv.SessionServer,
|
||||
alog: srv.AuditLog,
|
||||
context: *authContext,
|
||||
}
|
||||
@@ -2331,7 +2329,6 @@ func TestKindClusterConfig(t *testing.T) {
|
||||
require.NoError(t, err, trace.DebugReport(err))
|
||||
s := &ServerWithRoles{
|
||||
authServer: srv.AuthServer,
|
||||
sessions: srv.SessionServer,
|
||||
alog: srv.AuditLog,
|
||||
context: *authContext,
|
||||
}
|
||||
@@ -2891,7 +2888,6 @@ func TestListResources_KindKubernetesCluster(t *testing.T) {
|
||||
|
||||
s := &ServerWithRoles{
|
||||
authServer: srv.AuthServer,
|
||||
sessions: srv.SessionServer,
|
||||
alog: srv.AuditLog,
|
||||
context: *authContext,
|
||||
}
|
||||
|
||||
@@ -287,70 +287,6 @@ func (c *Client) ProcessKubeCSR(req KubeCSR) (*KubeCSRResponse, error) {
|
||||
return &re, nil
|
||||
}
|
||||
|
||||
// GetSessions returns a list of active sessions in the cluster as reported by
|
||||
// the auth server.
|
||||
func (c *Client) GetSessions(ctx context.Context, namespace string) ([]session.Session, error) {
|
||||
if namespace == "" {
|
||||
return nil, trace.BadParameter(MissingNamespaceError)
|
||||
}
|
||||
out, err := c.Get(ctx, c.Endpoint("namespaces", namespace, "sessions"), url.Values{})
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
var sessions []session.Session
|
||||
if err := json.Unmarshal(out.Bytes(), &sessions); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return sessions, nil
|
||||
}
|
||||
|
||||
// GetSession returns a session by ID
|
||||
func (c *Client) GetSession(ctx context.Context, namespace string, id session.ID) (*session.Session, error) {
|
||||
if namespace == "" {
|
||||
return nil, trace.BadParameter(MissingNamespaceError)
|
||||
}
|
||||
// saving extra round-trip
|
||||
if err := id.Check(); err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
out, err := c.Get(ctx, c.Endpoint("namespaces", namespace, "sessions", string(id)), url.Values{})
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
var sess *session.Session
|
||||
if err := json.Unmarshal(out.Bytes(), &sess); err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
return sess, nil
|
||||
}
|
||||
|
||||
// DeleteSession removes an active session from the backend.
|
||||
func (c *Client) DeleteSession(ctx context.Context, namespace string, id session.ID) error {
|
||||
if namespace == "" {
|
||||
return trace.BadParameter(MissingNamespaceError)
|
||||
}
|
||||
_, err := c.Delete(ctx, c.Endpoint("namespaces", namespace, "sessions", string(id)))
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
// CreateSession creates new session
|
||||
func (c *Client) CreateSession(ctx context.Context, sess session.Session) error {
|
||||
if sess.Namespace == "" {
|
||||
return trace.BadParameter(MissingNamespaceError)
|
||||
}
|
||||
_, err := c.PostJSON(ctx, c.Endpoint("namespaces", sess.Namespace, "sessions"), createSessionReq{Session: sess})
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
// UpdateSession updates existing session
|
||||
func (c *Client) UpdateSession(ctx context.Context, req session.UpdateRequest) error {
|
||||
if err := req.Check(); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
_, err := c.PutJSON(ctx, c.Endpoint("namespaces", req.Namespace, "sessions", string(req.ID)), updateSessionReq{Update: req})
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
func (c *Client) Close() error {
|
||||
c.HTTPClient.Close()
|
||||
return c.APIClient.Close()
|
||||
@@ -1680,7 +1616,6 @@ type ClientI interface {
|
||||
services.Databases
|
||||
services.WindowsDesktops
|
||||
WebService
|
||||
session.Service
|
||||
services.ClusterConfiguration
|
||||
services.SessionTrackerService
|
||||
services.ConnectionsDiagnostic
|
||||
|
||||
@@ -32,7 +32,6 @@ import (
|
||||
apiclient "github.com/gravitational/teleport/api/client"
|
||||
"github.com/gravitational/teleport/api/types"
|
||||
"github.com/gravitational/teleport/lib/auth/testauthority"
|
||||
"github.com/gravitational/teleport/lib/session"
|
||||
"github.com/gravitational/teleport/lib/tlsca"
|
||||
)
|
||||
|
||||
@@ -77,7 +76,8 @@ func TestClient_DialTimeout(t *testing.T) {
|
||||
errChan := make(chan error, 1)
|
||||
go func() {
|
||||
// try to create a session - this will timeout after the DialTimeout threshold is exceeded
|
||||
errChan <- clt.CreateSession(context.Background(), session.Session{Namespace: "test"})
|
||||
_, err := clt.CreateSessionTracker(context.Background(), &types.SessionTrackerV1{})
|
||||
errChan <- err
|
||||
}()
|
||||
|
||||
select {
|
||||
|
||||
@@ -4304,7 +4304,6 @@ func serverWithNopRole(cfg GRPCServerConfig) (*ServerWithRoles, error) {
|
||||
return &ServerWithRoles{
|
||||
authServer: cfg.AuthServer,
|
||||
context: *nopCtx,
|
||||
sessions: cfg.SessionService,
|
||||
alog: cfg.AuthServer,
|
||||
}, nil
|
||||
}
|
||||
@@ -4339,7 +4338,6 @@ func (g *GRPCServer) authenticate(ctx context.Context) (*grpcContext, error) {
|
||||
ServerWithRoles: &ServerWithRoles{
|
||||
authServer: g.AuthServer,
|
||||
context: *authContext,
|
||||
sessions: g.SessionService,
|
||||
alog: g.AuthServer,
|
||||
},
|
||||
}, nil
|
||||
|
||||
+5
-14
@@ -48,7 +48,6 @@ import (
|
||||
"github.com/gravitational/teleport/lib/services"
|
||||
"github.com/gravitational/teleport/lib/services/local"
|
||||
"github.com/gravitational/teleport/lib/services/suite"
|
||||
"github.com/gravitational/teleport/lib/session"
|
||||
"github.com/gravitational/teleport/lib/tlsca"
|
||||
"github.com/gravitational/teleport/lib/utils"
|
||||
)
|
||||
@@ -182,8 +181,6 @@ type TestAuthServer struct {
|
||||
AuthServer *Server
|
||||
// AuditLog is an event audit log
|
||||
AuditLog events.IAuditLog
|
||||
// SessionServer is a session service
|
||||
SessionServer session.Service
|
||||
// Backend is a backend for auth server
|
||||
Backend backend.Backend
|
||||
// Authorizer is an authorizer used in tests
|
||||
@@ -229,11 +226,6 @@ func NewTestAuthServer(cfg TestAuthServerConfig) (*TestAuthServer, error) {
|
||||
srv.AuditLog = localLog
|
||||
}
|
||||
|
||||
srv.SessionServer, err = session.New(srv.Backend)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
access := local.NewAccessService(srv.Backend)
|
||||
identity := local.NewIdentityService(srv.Backend)
|
||||
|
||||
@@ -541,11 +533,10 @@ func (a *TestAuthServer) Trust(ctx context.Context, remote *TestAuthServer, role
|
||||
// NewTestTLSServer returns new test TLS server
|
||||
func (a *TestAuthServer) NewTestTLSServer() (*TestTLSServer, error) {
|
||||
apiConfig := &APIConfig{
|
||||
AuthServer: a.AuthServer,
|
||||
Authorizer: a.Authorizer,
|
||||
SessionService: a.SessionServer,
|
||||
AuditLog: a.AuditLog,
|
||||
Emitter: a.AuthServer.emitter,
|
||||
AuthServer: a.AuthServer,
|
||||
Authorizer: a.Authorizer,
|
||||
AuditLog: a.AuditLog,
|
||||
Emitter: a.AuthServer.emitter,
|
||||
}
|
||||
srv, err := NewTestTLSServer(TestTLSServerConfig{
|
||||
APIConfig: apiConfig,
|
||||
@@ -658,7 +649,7 @@ func NewTestTLSServer(cfg TestTLSServerConfig) (*TestTLSServer, error) {
|
||||
}
|
||||
tlsConfig.Time = cfg.AuthServer.Clock().Now
|
||||
|
||||
accessPoint, err := NewAdminAuthServer(srv.AuthServer.AuthServer, srv.AuthServer.SessionServer, srv.AuthServer.AuditLog)
|
||||
accessPoint, err := NewAdminAuthServer(srv.AuthServer.AuthServer, srv.AuthServer.AuditLog)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
+9
-16
@@ -97,7 +97,6 @@ import (
|
||||
"github.com/gravitational/teleport/lib/reversetunnel"
|
||||
"github.com/gravitational/teleport/lib/services"
|
||||
"github.com/gravitational/teleport/lib/services/local"
|
||||
"github.com/gravitational/teleport/lib/session"
|
||||
"github.com/gravitational/teleport/lib/srv"
|
||||
"github.com/gravitational/teleport/lib/srv/alpnproxy"
|
||||
alpnproxyauth "github.com/gravitational/teleport/lib/srv/alpnproxy/auth"
|
||||
@@ -509,10 +508,11 @@ func (process *TeleportProcess) addConnector(connector *Connector) {
|
||||
// the resulting payload as a *Connector. Returns (nil, nil) when the
|
||||
// ExitContext is done, so error checking should happen on the connector rather
|
||||
// than the error:
|
||||
// conn, err := process.waitForConnector("FooIdentity", log)
|
||||
// if conn == nil {
|
||||
// return trace.Wrap(err)
|
||||
// }
|
||||
//
|
||||
// conn, err := process.waitForConnector("FooIdentity", log)
|
||||
// if conn == nil {
|
||||
// return trace.Wrap(err)
|
||||
// }
|
||||
func (process *TeleportProcess) waitForConnector(identityEvent string, log logrus.FieldLogger) (*Connector, error) {
|
||||
event, err := process.WaitForEvent(process.ExitContext(), identityEvent)
|
||||
if err != nil {
|
||||
@@ -1624,17 +1624,12 @@ func (process *TeleportProcess) initAuthService() error {
|
||||
// second, create the API Server: it's actually a collection of API servers,
|
||||
// each serving requests for a "role" which is assigned to every connected
|
||||
// client based on their certificate (user, server, admin, etc)
|
||||
sessionService, err := session.New(b)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
authorizer, err := auth.NewAuthorizer(clusterName, authServer, lockWatcher)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
apiConf := &auth.APIConfig{
|
||||
AuthServer: authServer,
|
||||
SessionService: sessionService,
|
||||
Authorizer: authorizer,
|
||||
AuditLog: process.auditLog,
|
||||
PluginRegistry: process.PluginRegistry,
|
||||
@@ -2270,7 +2265,6 @@ func (process *TeleportProcess) initSSH() error {
|
||||
regular.SetLimiter(limiter),
|
||||
regular.SetShell(cfg.SSH.Shell),
|
||||
regular.SetEmitter(&events.StreamerAndEmitter{Emitter: asyncEmitter, Streamer: streamer}),
|
||||
regular.SetSessionServer(conn.Client),
|
||||
regular.SetLabels(cfg.SSH.Labels, cfg.SSH.CmdLabels, process.cloudLabels),
|
||||
regular.SetNamespace(namespace),
|
||||
regular.SetPermitUserEnvironment(cfg.SSH.PermitUserEnvironment),
|
||||
@@ -2843,10 +2837,10 @@ func (process *TeleportProcess) getAdditionalPrincipals(role types.SystemRole) (
|
||||
|
||||
// initProxy gets called if teleport runs with 'proxy' role enabled.
|
||||
// this means it will do four things:
|
||||
// 1. serve a web UI
|
||||
// 2. proxy SSH connections to nodes running with 'node' role
|
||||
// 3. take care of reverse tunnels
|
||||
// 4. optionally proxy kubernetes connections
|
||||
// 1. serve a web UI
|
||||
// 2. proxy SSH connections to nodes running with 'node' role
|
||||
// 3. take care of reverse tunnels
|
||||
// 4. optionally proxy kubernetes connections
|
||||
func (process *TeleportProcess) initProxy() error {
|
||||
// If no TLS key was provided for the web listener, generate a self-signed cert
|
||||
if len(process.Config.Proxy.KeyPairs) == 0 &&
|
||||
@@ -3497,7 +3491,6 @@ func (process *TeleportProcess) initProxyEndpoint(conn *Connector) error {
|
||||
conn.Client,
|
||||
regular.SetLimiter(proxyLimiter),
|
||||
regular.SetProxyMode(peerAddrString, tsrv, accessPoint),
|
||||
regular.SetSessionServer(conn.Client),
|
||||
regular.SetCiphers(cfg.Ciphers),
|
||||
regular.SetKEXAlgorithms(cfg.KEXAlgorithms),
|
||||
regular.SetMACAlgorithms(cfg.MACAlgorithms),
|
||||
|
||||
@@ -19,21 +19,15 @@ limitations under the License.
|
||||
package session
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/jonboulle/clockwork"
|
||||
"github.com/moby/term"
|
||||
|
||||
"github.com/gravitational/teleport/api/types"
|
||||
"github.com/gravitational/teleport/lib/backend"
|
||||
"github.com/gravitational/teleport/lib/defaults"
|
||||
|
||||
"github.com/gravitational/trace"
|
||||
)
|
||||
@@ -199,293 +193,10 @@ func (p *TerminalParams) Winsize() *term.Winsize {
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateRequest is a session update request
|
||||
type UpdateRequest struct {
|
||||
ID ID `json:"id"`
|
||||
Namespace string `json:"namespace"`
|
||||
TerminalParams *TerminalParams `json:"terminal_params"`
|
||||
|
||||
// Parties allows to update the list of session parties. nil means
|
||||
// "do not update", empty list means "everybody is gone"
|
||||
Parties *[]Party `json:"parties"`
|
||||
}
|
||||
|
||||
// Check returns nil if request is valid, error otherwize
|
||||
func (u *UpdateRequest) Check() error {
|
||||
if err := u.ID.Check(); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
if u.Namespace == "" {
|
||||
return trace.BadParameter("missing parameter Namespace")
|
||||
}
|
||||
if u.TerminalParams != nil {
|
||||
_, err := NewTerminalParamsFromInt(u.TerminalParams.W, u.TerminalParams.H)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MaxSessionSliceLength is the maximum number of sessions per time window
|
||||
// that the backend will return.
|
||||
const MaxSessionSliceLength = 1000
|
||||
|
||||
// Service is a realtime SSH session service that has information about
|
||||
// sessions that are in-flight in the cluster at the moment.
|
||||
type Service interface {
|
||||
// GetSessions returns a list of currently active sessions matching
|
||||
// the given condition.
|
||||
GetSessions(ctx context.Context, namespace string) ([]Session, error)
|
||||
|
||||
// GetSession returns a session with its parties by ID.
|
||||
GetSession(ctx context.Context, namespace string, id ID) (*Session, error)
|
||||
|
||||
// CreateSession creates a new active session and it's parameters if term is
|
||||
// skipped, terminal size won't be recorded.
|
||||
CreateSession(ctx context.Context, sess Session) error
|
||||
|
||||
// UpdateSession updates certain session parameters (last_active, terminal
|
||||
// parameters) other parameters will not be updated.
|
||||
UpdateSession(ctx context.Context, req UpdateRequest) error
|
||||
|
||||
// DeleteSession removes an active session from the backend.
|
||||
DeleteSession(ctx context.Context, namespace string, id ID) error
|
||||
}
|
||||
|
||||
type server struct {
|
||||
bk backend.Backend
|
||||
activeSessionTTL time.Duration
|
||||
clock clockwork.Clock
|
||||
}
|
||||
|
||||
// New returns new session server that uses sqlite to manage
|
||||
// active sessions
|
||||
func New(bk backend.Backend) (Service, error) {
|
||||
s := &server{
|
||||
bk: bk,
|
||||
clock: clockwork.NewRealClock(),
|
||||
}
|
||||
if s.activeSessionTTL == 0 {
|
||||
s.activeSessionTTL = defaults.ActiveSessionTTL
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func activePrefix(namespace string) []byte {
|
||||
return backend.Key("namespaces", namespace, "sessions", "active")
|
||||
}
|
||||
|
||||
func activeKey(namespace string, key string) []byte {
|
||||
return backend.Key("namespaces", namespace, "sessions", "active", key)
|
||||
}
|
||||
|
||||
// GetSessions returns a list of active sessions.
|
||||
// Returns an empty slice if no sessions are active
|
||||
func (s *server) GetSessions(ctx context.Context, namespace string) ([]Session, error) {
|
||||
prefix := activePrefix(namespace)
|
||||
result, err := s.bk.GetRange(ctx, prefix, backend.RangeEnd(prefix), MaxSessionSliceLength)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
sessions := make(Sessions, len(result.Items))
|
||||
for i, item := range result.Items {
|
||||
if err := json.Unmarshal(item.Value, &sessions[i]); err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
}
|
||||
|
||||
sort.Stable(sessions)
|
||||
return sessions, nil
|
||||
}
|
||||
|
||||
// Sessions type is created over []Session to implement sort.Interface to
|
||||
// be able to sort sessions by creation time
|
||||
type Sessions []Session
|
||||
|
||||
// Swap is part of sort.Interface implementation for []Session
|
||||
func (slice Sessions) Swap(i, j int) {
|
||||
s := slice[i]
|
||||
slice[i] = slice[j]
|
||||
slice[j] = s
|
||||
}
|
||||
|
||||
// Less is part of sort.Interface implementation for []Session
|
||||
func (slice Sessions) Less(i, j int) bool {
|
||||
return slice[i].Created.Before(slice[j].Created)
|
||||
}
|
||||
|
||||
// Len is part of sort.Interface implementation for []Session
|
||||
func (slice Sessions) Len() int {
|
||||
return len(slice)
|
||||
}
|
||||
|
||||
// GetSession returns the session by its id. Returns NotFound if a session
|
||||
// is not found
|
||||
func (s *server) GetSession(ctx context.Context, namespace string, id ID) (*Session, error) {
|
||||
item, err := s.bk.Get(ctx, activeKey(namespace, string(id)))
|
||||
if err != nil {
|
||||
if trace.IsNotFound(err) {
|
||||
return nil, trace.NotFound("session(%v, %v) is not found", namespace, id)
|
||||
}
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
var sess Session
|
||||
if err := json.Unmarshal(item.Value, &sess); err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
return &sess, nil
|
||||
}
|
||||
|
||||
// CreateSession creates a new session if it does not exist, if the session
|
||||
// exists the function will return AlreadyExists error
|
||||
// The session will be marked as active for TTL period of time
|
||||
func (s *server) CreateSession(ctx context.Context, sess Session) error {
|
||||
if err := sess.ID.Check(); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
if sess.Namespace == "" {
|
||||
return trace.BadParameter("session namespace can not be empty")
|
||||
}
|
||||
if sess.Login == "" {
|
||||
return trace.BadParameter("session login can not be empty")
|
||||
}
|
||||
if sess.Created.IsZero() {
|
||||
return trace.BadParameter("created can not be empty")
|
||||
}
|
||||
if sess.LastActive.IsZero() {
|
||||
return trace.BadParameter("last_active can not be empty")
|
||||
}
|
||||
_, err := NewTerminalParamsFromInt(sess.TerminalParams.W, sess.TerminalParams.H)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
sess.Parties = nil
|
||||
data, err := json.Marshal(sess)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
item := backend.Item{
|
||||
Key: activeKey(sess.Namespace, string(sess.ID)),
|
||||
Value: data,
|
||||
Expires: s.clock.Now().UTC().Add(s.activeSessionTTL),
|
||||
}
|
||||
_, err = s.bk.Create(ctx, item)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
const (
|
||||
sessionUpdateAttempts = 10
|
||||
sessionUpdateRetryPeriod = 20 * time.Millisecond
|
||||
)
|
||||
|
||||
// UpdateSession updates session parameters - can mark it as inactive and update its terminal parameters
|
||||
func (s *server) UpdateSession(ctx context.Context, req UpdateRequest) error {
|
||||
if err := req.Check(); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
key := activeKey(req.Namespace, string(req.ID))
|
||||
|
||||
// Try several times, then give up
|
||||
for i := 0; i < sessionUpdateAttempts; i++ {
|
||||
item, err := s.bk.Get(ctx, key)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
var session Session
|
||||
if err := json.Unmarshal(item.Value, &session); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
if req.TerminalParams != nil {
|
||||
session.TerminalParams = *req.TerminalParams
|
||||
}
|
||||
if req.Parties != nil {
|
||||
session.Parties = *req.Parties
|
||||
}
|
||||
newValue, err := json.Marshal(session)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
newItem := backend.Item{
|
||||
Key: key,
|
||||
Value: newValue,
|
||||
Expires: s.clock.Now().UTC().Add(s.activeSessionTTL),
|
||||
}
|
||||
|
||||
_, err = s.bk.CompareAndSwap(ctx, *item, newItem)
|
||||
if err != nil {
|
||||
if trace.IsCompareFailed(err) || trace.IsConnectionProblem(err) {
|
||||
s.clock.Sleep(sessionUpdateRetryPeriod)
|
||||
continue
|
||||
}
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return trace.ConnectionProblem(nil, "failed concurrently update the session")
|
||||
}
|
||||
|
||||
// DeleteSession removes an active session from the backend.
|
||||
func (s *server) DeleteSession(ctx context.Context, namespace string, id ID) error {
|
||||
if !types.IsValidNamespace(namespace) {
|
||||
return trace.BadParameter("invalid namespace %q", namespace)
|
||||
}
|
||||
err := id.Check()
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
err = s.bk.Delete(ctx, activeKey(namespace, string(id)))
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// discardSessionServer discards all information about sessions given to it.
|
||||
type discardSessionServer struct {
|
||||
}
|
||||
|
||||
// NewDiscardSessionServer returns a new discarding session server. It's used
|
||||
// with the recording proxy so that nodes don't register active sessions to
|
||||
// the backend.
|
||||
func NewDiscardSessionServer() Service {
|
||||
return &discardSessionServer{}
|
||||
}
|
||||
|
||||
// GetSessions returns an empty list of sessions.
|
||||
func (d *discardSessionServer) GetSessions(ctx context.Context, namespace string) ([]Session, error) {
|
||||
return []Session{}, nil
|
||||
}
|
||||
|
||||
// GetSession always returns a zero session.
|
||||
func (d *discardSessionServer) GetSession(ctx context.Context, namespace string, id ID) (*Session, error) {
|
||||
return &Session{}, nil
|
||||
}
|
||||
|
||||
// CreateSession always returns nil, does nothing.
|
||||
func (d *discardSessionServer) CreateSession(ctx context.Context, sess Session) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateSession always returns nil, does nothing.
|
||||
func (d *discardSessionServer) UpdateSession(ctx context.Context, req UpdateRequest) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteSession removes an active session from the backend.
|
||||
func (d *discardSessionServer) DeleteSession(ctx context.Context, namespace string, id ID) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewTerminalParamsFromUint32 returns new terminal parameters from uint32 width and height
|
||||
func NewTerminalParamsFromUint32(w uint32, h uint32) (*TerminalParams, error) {
|
||||
if w > maxSize || w < minSize {
|
||||
|
||||
@@ -1,227 +0,0 @@
|
||||
/*
|
||||
Copyright 2015 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 session
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
apidefaults "github.com/gravitational/teleport/api/defaults"
|
||||
"github.com/gravitational/teleport/lib/backend"
|
||||
"github.com/gravitational/teleport/lib/backend/lite"
|
||||
"github.com/gravitational/teleport/lib/defaults"
|
||||
"github.com/gravitational/teleport/lib/utils"
|
||||
|
||||
"github.com/jonboulle/clockwork"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/gravitational/trace"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
utils.InitLoggerForTests()
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func TestSessions(t *testing.T) {
|
||||
s := newsessionSuite(t)
|
||||
t.Cleanup(func() { s.TearDown(t) })
|
||||
|
||||
t.Run("TestID", s.TestID)
|
||||
t.Run("TestSessionsCRUD", s.TestSessionsCRUD)
|
||||
t.Run("TestSessionsInactivity", s.TestSessionsInactivity)
|
||||
t.Run("TestPartiesCRUD", s.TestPartiesCRUD)
|
||||
}
|
||||
|
||||
type sessionSuite struct {
|
||||
dir string
|
||||
srv *server
|
||||
bk backend.Backend
|
||||
clock clockwork.FakeClock
|
||||
}
|
||||
|
||||
func newsessionSuite(t *testing.T) *sessionSuite {
|
||||
var err error
|
||||
s := &sessionSuite{}
|
||||
|
||||
s.clock = clockwork.NewFakeClockAt(time.Date(2016, 9, 8, 7, 6, 5, 0, time.UTC))
|
||||
s.dir = t.TempDir()
|
||||
s.bk, err = lite.NewWithConfig(context.TODO(),
|
||||
lite.Config{
|
||||
Path: s.dir,
|
||||
Clock: s.clock,
|
||||
},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
srv, err := New(s.bk)
|
||||
require.NoError(t, err)
|
||||
srv.(*server).clock = s.clock
|
||||
s.srv = srv.(*server)
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *sessionSuite) TearDown(t *testing.T) {
|
||||
require.NoError(t, s.bk.Close())
|
||||
}
|
||||
|
||||
func (s *sessionSuite) TestID(t *testing.T) {
|
||||
id := NewID()
|
||||
id2, err := ParseID(id.String())
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, id, *id2)
|
||||
|
||||
for _, val := range []string{"garbage", "", " ", string(id) + "extra"} {
|
||||
id := ID(val)
|
||||
require.Error(t, id.Check())
|
||||
}
|
||||
}
|
||||
|
||||
func (s *sessionSuite) TestSessionsCRUD(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
out, err := s.srv.GetSessions(ctx, apidefaults.Namespace)
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, out)
|
||||
|
||||
// Create session.
|
||||
sess := Session{
|
||||
ID: NewID(),
|
||||
Namespace: apidefaults.Namespace,
|
||||
TerminalParams: TerminalParams{W: 100, H: 100},
|
||||
Login: "bob",
|
||||
LastActive: s.clock.Now().UTC(),
|
||||
Created: s.clock.Now().UTC(),
|
||||
}
|
||||
require.NoError(t, s.srv.CreateSession(ctx, sess))
|
||||
|
||||
// Make sure only one session exists.
|
||||
out, err = s.srv.GetSessions(ctx, apidefaults.Namespace)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, out, []Session{sess})
|
||||
|
||||
// Make sure the session is the one created above.
|
||||
s2, err := s.srv.GetSession(ctx, apidefaults.Namespace, sess.ID)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, s2, &sess)
|
||||
|
||||
// Update session terminal parameter
|
||||
err = s.srv.UpdateSession(ctx, UpdateRequest{
|
||||
ID: sess.ID,
|
||||
Namespace: apidefaults.Namespace,
|
||||
TerminalParams: &TerminalParams{W: 101, H: 101},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify update was applied.
|
||||
sess.TerminalParams = TerminalParams{W: 101, H: 101}
|
||||
s2, err = s.srv.GetSession(ctx, apidefaults.Namespace, sess.ID)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, s2, &sess)
|
||||
|
||||
// Remove the session.
|
||||
err = s.srv.DeleteSession(ctx, apidefaults.Namespace, sess.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Make sure session no longer exists.
|
||||
_, err = s.srv.GetSession(ctx, apidefaults.Namespace, sess.ID)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
// TestSessionsInactivity makes sure that session will be marked
|
||||
// as inactive after period of inactivity
|
||||
func (s *sessionSuite) TestSessionsInactivity(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
sess := Session{
|
||||
ID: NewID(),
|
||||
Namespace: apidefaults.Namespace,
|
||||
TerminalParams: TerminalParams{W: 100, H: 100},
|
||||
Login: "bob",
|
||||
LastActive: s.clock.Now().UTC(),
|
||||
Created: s.clock.Now().UTC(),
|
||||
}
|
||||
require.NoError(t, s.srv.CreateSession(ctx, sess))
|
||||
|
||||
// move forward in time:
|
||||
s.clock.Advance(defaults.ActiveSessionTTL + time.Second)
|
||||
|
||||
// should not be in active sessions:
|
||||
s2, err := s.srv.GetSession(ctx, apidefaults.Namespace, sess.ID)
|
||||
require.IsType(t, trace.NotFound(""), err)
|
||||
require.Nil(t, s2)
|
||||
}
|
||||
|
||||
func (s *sessionSuite) TestPartiesCRUD(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
// create session:
|
||||
sess := Session{
|
||||
ID: NewID(),
|
||||
Namespace: apidefaults.Namespace,
|
||||
TerminalParams: TerminalParams{W: 100, H: 100},
|
||||
Login: "vincent",
|
||||
LastActive: s.clock.Now().UTC(),
|
||||
Created: s.clock.Now().UTC(),
|
||||
}
|
||||
err := s.srv.CreateSession(ctx, sess)
|
||||
require.NoError(t, err)
|
||||
// add two people:
|
||||
parties := []Party{
|
||||
{
|
||||
ID: NewID(),
|
||||
RemoteAddr: "1_remote_addr",
|
||||
User: "first",
|
||||
ServerID: "luna",
|
||||
LastActive: s.clock.Now().UTC(),
|
||||
},
|
||||
{
|
||||
ID: NewID(),
|
||||
RemoteAddr: "2_remote_addr",
|
||||
User: "second",
|
||||
ServerID: "luna",
|
||||
LastActive: s.clock.Now().UTC(),
|
||||
},
|
||||
}
|
||||
err = s.srv.UpdateSession(ctx, UpdateRequest{
|
||||
ID: sess.ID,
|
||||
Namespace: apidefaults.Namespace,
|
||||
Parties: &parties,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
// verify they're in the session:
|
||||
copy, err := s.srv.GetSession(ctx, apidefaults.Namespace, sess.ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, copy.Parties, 2)
|
||||
|
||||
// empty update (list of parties must not change)
|
||||
err = s.srv.UpdateSession(ctx, UpdateRequest{ID: sess.ID, Namespace: apidefaults.Namespace})
|
||||
require.NoError(t, err)
|
||||
copy, _ = s.srv.GetSession(ctx, apidefaults.Namespace, sess.ID)
|
||||
require.Len(t, copy.Parties, 2)
|
||||
|
||||
// remove the 2nd party:
|
||||
deleted := copy.RemoveParty(parties[1].ID)
|
||||
require.True(t, deleted)
|
||||
err = s.srv.UpdateSession(ctx, UpdateRequest{ID: copy.ID, Parties: ©.Parties, Namespace: apidefaults.Namespace})
|
||||
require.NoError(t, err)
|
||||
copy, _ = s.srv.GetSession(ctx, apidefaults.Namespace, sess.ID)
|
||||
require.Len(t, copy.Parties, 1)
|
||||
|
||||
// we still have the 1st party in:
|
||||
require.Equal(t, parties[0].ID, copy.Parties[0].ID)
|
||||
}
|
||||
@@ -132,9 +132,6 @@ type Server interface {
|
||||
// GetAccessPoint returns an AccessPoint for this cluster.
|
||||
GetAccessPoint() AccessPoint
|
||||
|
||||
// GetSessionServer returns a session server.
|
||||
GetSessionServer() rsession.Service
|
||||
|
||||
// GetDataDir returns data directory of the server
|
||||
GetDataDir() string
|
||||
|
||||
|
||||
@@ -38,7 +38,6 @@ import (
|
||||
"github.com/gravitational/teleport/lib/pam"
|
||||
restricted "github.com/gravitational/teleport/lib/restrictedsession"
|
||||
"github.com/gravitational/teleport/lib/services"
|
||||
"github.com/gravitational/teleport/lib/session"
|
||||
"github.com/gravitational/teleport/lib/srv"
|
||||
"github.com/gravitational/teleport/lib/sshutils"
|
||||
"github.com/gravitational/teleport/lib/sshutils/x11"
|
||||
@@ -140,7 +139,6 @@ type Server struct {
|
||||
authClient auth.ClientI
|
||||
authService srv.AccessPoint
|
||||
sessionRegistry *srv.SessionRegistry
|
||||
sessionServer session.Service
|
||||
dataDir string
|
||||
|
||||
clock clockwork.Clock
|
||||
@@ -300,7 +298,6 @@ func New(c ServerConfig) (*Server, error) {
|
||||
address: c.Address,
|
||||
authClient: c.AuthClient,
|
||||
authService: c.AuthClient,
|
||||
sessionServer: c.AuthClient,
|
||||
dataDir: c.DataDir,
|
||||
clock: c.Clock,
|
||||
hostUUID: c.HostUUID,
|
||||
@@ -414,11 +411,6 @@ func (s *Server) GetAccessPoint() srv.AccessPoint {
|
||||
return s.authService
|
||||
}
|
||||
|
||||
// GetSessionServer returns a session server.
|
||||
func (s *Server) GetSessionServer() session.Service {
|
||||
return s.sessionServer
|
||||
}
|
||||
|
||||
// GetPAM returns the PAM configuration for a server. Because the forwarding
|
||||
// server runs in-memory, it does not support PAM.
|
||||
func (s *Server) GetPAM() (*pam.Config, error) {
|
||||
|
||||
@@ -38,7 +38,6 @@ import (
|
||||
"github.com/gravitational/teleport/lib/pam"
|
||||
restricted "github.com/gravitational/teleport/lib/restrictedsession"
|
||||
"github.com/gravitational/teleport/lib/services"
|
||||
rsession "github.com/gravitational/teleport/lib/session"
|
||||
"github.com/gravitational/teleport/lib/sshutils"
|
||||
"github.com/gravitational/teleport/lib/utils"
|
||||
"github.com/gravitational/trace"
|
||||
@@ -178,11 +177,6 @@ func (m *mockServer) GetAccessPoint() AccessPoint {
|
||||
return m.auth
|
||||
}
|
||||
|
||||
// GetSessionServer returns a session server.
|
||||
func (m *mockServer) GetSessionServer() rsession.Service {
|
||||
return rsession.NewDiscardSessionServer()
|
||||
}
|
||||
|
||||
// GetDataDir returns data directory of the server
|
||||
func (m *mockServer) GetDataDir() string {
|
||||
return "testDataDir"
|
||||
|
||||
@@ -50,7 +50,6 @@ import (
|
||||
"github.com/gravitational/teleport/lib/reversetunnel"
|
||||
"github.com/gravitational/teleport/lib/services"
|
||||
"github.com/gravitational/teleport/lib/services/local"
|
||||
rsession "github.com/gravitational/teleport/lib/session"
|
||||
"github.com/gravitational/teleport/lib/srv"
|
||||
"github.com/gravitational/teleport/lib/sshutils"
|
||||
"github.com/gravitational/teleport/lib/sshutils/x11"
|
||||
@@ -92,13 +91,12 @@ type Server struct {
|
||||
addr utils.NetAddr
|
||||
hostname string
|
||||
|
||||
srv *sshutils.Server
|
||||
shell string
|
||||
getRotation services.RotationGetter
|
||||
authService srv.AccessPoint
|
||||
reg *srv.SessionRegistry
|
||||
sessionServer rsession.Service
|
||||
limiter *limiter.Limiter
|
||||
srv *sshutils.Server
|
||||
shell string
|
||||
getRotation services.RotationGetter
|
||||
authService srv.AccessPoint
|
||||
reg *srv.SessionRegistry
|
||||
limiter *limiter.Limiter
|
||||
|
||||
inventoryHandle inventory.DownstreamHandle
|
||||
|
||||
@@ -253,13 +251,6 @@ func (s *Server) GetAccessPoint() srv.AccessPoint {
|
||||
return s.authService
|
||||
}
|
||||
|
||||
func (s *Server) GetSessionServer() rsession.Service {
|
||||
if s.isAuditedAtProxy() {
|
||||
return rsession.NewDiscardSessionServer()
|
||||
}
|
||||
return s.sessionServer
|
||||
}
|
||||
|
||||
// GetUtmpPath returns the optional override of the utmp and wtmp path.
|
||||
func (s *Server) GetUtmpPath() (string, string) {
|
||||
return s.utmpPath, s.wtmpPath
|
||||
@@ -303,24 +294,6 @@ func (s *Server) GetHostUsers() srv.HostUsers {
|
||||
return s.users
|
||||
}
|
||||
|
||||
// isAuditedAtProxy returns true if sessions are being recorded at the proxy
|
||||
// and this is a Teleport node.
|
||||
func (s *Server) isAuditedAtProxy() bool {
|
||||
// always be safe, better to double record than not record at all
|
||||
recConfig, err := s.GetAccessPoint().GetSessionRecordingConfig(s.ctx)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
isRecordAtProxy := services.IsRecordAtProxy(recConfig.GetMode())
|
||||
isTeleportNode := s.Component() == teleport.ComponentNode
|
||||
|
||||
if isRecordAtProxy && isTeleportNode {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ServerOption is a functional option passed to the server
|
||||
type ServerOption func(s *Server) error
|
||||
|
||||
@@ -444,14 +417,6 @@ func SetShell(shell string) ServerOption {
|
||||
}
|
||||
}
|
||||
|
||||
// SetSessionServer represents realtime session registry server
|
||||
func SetSessionServer(sessionServer rsession.Service) ServerOption {
|
||||
return func(s *Server) error {
|
||||
s.sessionServer = sessionServer
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// SetProxyMode starts this server in SSH proxying mode
|
||||
func SetProxyMode(peerAddr string, tsrv reversetunnel.Tunnel, ap auth.ReadProxyAccessPoint) ServerOption {
|
||||
return func(s *Server) error {
|
||||
|
||||
@@ -185,7 +185,6 @@ func newCustomFixture(t *testing.T, mutateCfg func(*auth.TestServerConfig), sshO
|
||||
SetNamespace(apidefaults.Namespace),
|
||||
SetEmitter(nodeClient),
|
||||
SetShell("/bin/sh"),
|
||||
SetSessionServer(nodeClient),
|
||||
SetPAMConfig(&pam.Config{Enabled: false}),
|
||||
SetLabels(
|
||||
map[string]string{"foo": "bar"},
|
||||
@@ -1193,7 +1192,6 @@ func TestProxyRoundRobin(t *testing.T) {
|
||||
utils.NetAddr{},
|
||||
proxyClient,
|
||||
SetProxyMode("", reverseTunnelServer, proxyClient),
|
||||
SetSessionServer(proxyClient),
|
||||
SetEmitter(nodeClient),
|
||||
SetNamespace(apidefaults.Namespace),
|
||||
SetPAMConfig(&pam.Config{Enabled: false}),
|
||||
@@ -1314,7 +1312,6 @@ func TestProxyDirectAccess(t *testing.T) {
|
||||
utils.NetAddr{},
|
||||
proxyClient,
|
||||
SetProxyMode("", reverseTunnelServer, proxyClient),
|
||||
SetSessionServer(proxyClient),
|
||||
SetEmitter(nodeClient),
|
||||
SetNamespace(apidefaults.Namespace),
|
||||
SetPAMConfig(&pam.Config{Enabled: false}),
|
||||
@@ -1485,7 +1482,6 @@ func TestLimiter(t *testing.T) {
|
||||
nodeClient,
|
||||
SetLimiter(limiter),
|
||||
SetShell("/bin/sh"),
|
||||
SetSessionServer(nodeClient),
|
||||
SetEmitter(nodeClient),
|
||||
SetNamespace(apidefaults.Namespace),
|
||||
SetPAMConfig(&pam.Config{Enabled: false}),
|
||||
@@ -1973,7 +1969,6 @@ func TestIgnorePuTTYSimpleChannel(t *testing.T) {
|
||||
utils.NetAddr{},
|
||||
proxyClient,
|
||||
SetProxyMode("", reverseTunnelServer, proxyClient),
|
||||
SetSessionServer(proxyClient),
|
||||
SetEmitter(nodeClient),
|
||||
SetNamespace(apidefaults.Namespace),
|
||||
SetPAMConfig(&pam.Config{Enabled: false}),
|
||||
|
||||
+8
-116
@@ -115,10 +115,6 @@ func (sc *SessionRegistryConfig) CheckAndSetDefaults() error {
|
||||
return trace.BadParameter("server is required")
|
||||
}
|
||||
|
||||
if sc.Srv.GetSessionServer() == nil {
|
||||
return trace.BadParameter("session server is required")
|
||||
}
|
||||
|
||||
if sc.clock == nil {
|
||||
sc.clock = sc.Srv.GetClock()
|
||||
}
|
||||
@@ -528,32 +524,6 @@ func newSession(ctx context.Context, id rsession.ID, r *SessionRegistry, scx *Se
|
||||
rsess.TerminalParams.H = int(winsize.Height)
|
||||
}
|
||||
|
||||
// get the session server where session information lives. if the recording
|
||||
// proxy is being used and this is a node, then a discard session server will
|
||||
// be returned here.
|
||||
sessionServer := r.Srv.GetSessionServer()
|
||||
|
||||
err := sessionServer.CreateSession(ctx, rsess)
|
||||
if err != nil {
|
||||
if trace.IsAlreadyExists(err) {
|
||||
// if session already exists, make sure they are compatible
|
||||
// Login matches existing login
|
||||
existing, err := sessionServer.GetSession(ctx, r.Srv.GetNamespace(), id)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
if existing.Login != rsess.Login && rsess.Login != teleport.SSHSessionJoinPrincipal {
|
||||
return nil, trace.AccessDenied(
|
||||
"can't switch users from %v to %v for session %v",
|
||||
rsess.Login, existing.Login, id)
|
||||
}
|
||||
}
|
||||
// return nil, trace.Wrap(err)
|
||||
// No need to abort. Perhaps the auth server is down?
|
||||
// Log the error and continue:
|
||||
r.log.Errorf("Failed to create new session: %v.", err)
|
||||
}
|
||||
|
||||
policySets := scx.Identity.AccessChecker.SessionPolicySets()
|
||||
|
||||
sess := &session{
|
||||
@@ -589,6 +559,7 @@ func newSession(ctx context.Context, id rsession.ID, r *SessionRegistry, scx *Se
|
||||
}
|
||||
}()
|
||||
|
||||
var err error
|
||||
if err = sess.trackSession(scx.Identity.TeleportUser, policySets); err != nil {
|
||||
if trace.IsNotImplemented(err) {
|
||||
return nil, trace.NotImplemented("Attempted to use Moderated Sessions with an Auth Server below the minimum version of 9.0.0.")
|
||||
@@ -686,16 +657,6 @@ func (s *session) Close() error {
|
||||
|
||||
s.registry.removeSession(s)
|
||||
|
||||
// Remove the session from the backend.
|
||||
if s.scx.srv.GetSessionServer() != nil {
|
||||
err := s.scx.srv.GetSessionServer().DeleteSession(s.serverCtx, s.scx.srv.GetNamespace(), s.id)
|
||||
if err != nil {
|
||||
s.log.Errorf("Failed to remove active session: %v: %v. "+
|
||||
"Access to backend may be degraded, check connectivity to backend.",
|
||||
s.id, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Complete the session recording
|
||||
if recorder := s.Recorder(); recorder != nil {
|
||||
if err := recorder.Complete(s.serverCtx); err != nil {
|
||||
@@ -1032,10 +993,6 @@ func (s *session) startInteractive(ctx context.Context, ch ssh.Channel, scx *Ser
|
||||
|
||||
scx.Debug("Got continue signal")
|
||||
|
||||
// Start a heartbeat that marks this session as active with current members
|
||||
// of party in the backend.
|
||||
go s.heartbeat(ctx, scx)
|
||||
|
||||
// wait for exec.Cmd (or receipt of "exit-status" for a forwarding node),
|
||||
// once it is received wait for the io.Copy above to finish, then broadcast
|
||||
// the "exit-status" to the client.
|
||||
@@ -1391,71 +1348,6 @@ func (s *session) lingerAndDie(ctx context.Context, party *party) {
|
||||
}
|
||||
}
|
||||
|
||||
// exportPartyMembers exports participants in the in-memory map of party
|
||||
// members.
|
||||
func (s *session) exportPartyMembers() []rsession.Party {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
var partyList []rsession.Party
|
||||
for _, p := range s.parties {
|
||||
partyList = append(partyList, rsession.Party{
|
||||
ID: p.id,
|
||||
User: p.user,
|
||||
ServerID: p.serverID,
|
||||
RemoteAddr: p.site,
|
||||
LastActive: p.getLastActive(),
|
||||
})
|
||||
}
|
||||
|
||||
return partyList
|
||||
}
|
||||
|
||||
// heartbeat will loop as long as the session is not closed and mark it as
|
||||
// active and update the list of party members. If the session are recorded at
|
||||
// the proxy, then this function does nothing as it's counterpart
|
||||
// in the proxy will do this work.
|
||||
func (s *session) heartbeat(ctx context.Context, scx *ServerContext) {
|
||||
// If sessions are being recorded at the proxy, an identical version of this
|
||||
// goroutine is running in the proxy, which means it does not need to run here.
|
||||
if services.IsRecordAtProxy(scx.SessionRecordingConfig.GetMode()) &&
|
||||
s.registry.Srv.Component() == teleport.ComponentNode {
|
||||
return
|
||||
}
|
||||
|
||||
// If no session server (endpoint interface for active sessions) is passed in
|
||||
// (for example Teleconsole does this) then nothing to sync.
|
||||
sessionServer := s.registry.Srv.GetSessionServer()
|
||||
if sessionServer == nil {
|
||||
return
|
||||
}
|
||||
|
||||
s.log.Debugf("Starting poll and sync of terminal size to all parties.")
|
||||
defer s.log.Debugf("Stopping poll and sync of terminal size to all parties.")
|
||||
|
||||
tickerCh := time.NewTicker(defaults.SessionRefreshPeriod)
|
||||
defer tickerCh.Stop()
|
||||
|
||||
// Loop as long as the session is active, updating the session in the backend.
|
||||
for {
|
||||
select {
|
||||
case <-tickerCh.C:
|
||||
partyList := s.exportPartyMembers()
|
||||
|
||||
err := sessionServer.UpdateSession(ctx, rsession.UpdateRequest{
|
||||
Namespace: scx.srv.GetNamespace(),
|
||||
ID: s.id,
|
||||
Parties: &partyList,
|
||||
})
|
||||
if err != nil {
|
||||
s.log.Warnf("Unable to update session %v as active: %v", s.id, err)
|
||||
}
|
||||
case <-s.stopC:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *session) checkPresence() error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
@@ -1679,12 +1571,6 @@ func (p *party) updateActivity() {
|
||||
p.lastActive = time.Now()
|
||||
}
|
||||
|
||||
func (p *party) getLastActive() time.Time {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
return p.lastActive
|
||||
}
|
||||
|
||||
func (p *party) Read(bytes []byte) (int, error) {
|
||||
p.updateActivity()
|
||||
return p.ch.Read(bytes)
|
||||
@@ -1745,7 +1631,13 @@ func (s *session) trackSession(teleportUser string, policySet []*types.SessionTr
|
||||
|
||||
s.log.Debug("Creating session tracker")
|
||||
var err error
|
||||
s.tracker, err = NewSessionTracker(s.serverCtx, trackerSpec, s.registry.SessionTrackerService)
|
||||
|
||||
// if doing proxy recording, don't propagate tracker to the cluster level
|
||||
if s.registry.Srv.Component() == teleport.ComponentNode && services.IsRecordAtProxy(s.scx.SessionRecordingConfig.GetMode()) {
|
||||
s.tracker, err = NewSessionTracker(s.serverCtx, trackerSpec, nil)
|
||||
} else {
|
||||
s.tracker, err = NewSessionTracker(s.serverCtx, trackerSpec, s.registry.SessionTrackerService)
|
||||
}
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
+56
-38
@@ -45,17 +45,15 @@ type SessionTracker struct {
|
||||
|
||||
// NewSessionTracker returns a new SessionTracker for the given types.SessionTracker
|
||||
func NewSessionTracker(ctx context.Context, trackerSpec types.SessionTrackerSpecV1, service services.SessionTrackerService) (*SessionTracker, error) {
|
||||
if service == nil {
|
||||
return nil, trace.BadParameter("missing parameter service")
|
||||
}
|
||||
|
||||
t, err := types.NewSessionTracker(trackerSpec)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
if t, err = service.CreateSessionTracker(ctx, t); err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
if service != nil {
|
||||
if t, err = service.CreateSessionTracker(ctx, t); err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
}
|
||||
|
||||
return &SessionTracker{
|
||||
@@ -106,15 +104,20 @@ func (s *SessionTracker) UpdateExpiration(ctx context.Context, expiry time.Time)
|
||||
s.tracker.SetExpiry(expiry)
|
||||
s.trackerCond.Broadcast()
|
||||
|
||||
err := s.service.UpdateSessionTracker(ctx, &proto.UpdateSessionTrackerRequest{
|
||||
SessionID: s.tracker.GetSessionID(),
|
||||
Update: &proto.UpdateSessionTrackerRequest_UpdateExpiry{
|
||||
UpdateExpiry: &proto.SessionTrackerUpdateExpiry{
|
||||
Expires: &expiry,
|
||||
if s.service != nil {
|
||||
err := s.service.UpdateSessionTracker(ctx, &proto.UpdateSessionTrackerRequest{
|
||||
SessionID: s.tracker.GetSessionID(),
|
||||
Update: &proto.UpdateSessionTrackerRequest_UpdateExpiry{
|
||||
UpdateExpiry: &proto.SessionTrackerUpdateExpiry{
|
||||
Expires: &expiry,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
return trace.Wrap(err)
|
||||
})
|
||||
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SessionTracker) AddParticipant(ctx context.Context, p *types.Participant) error {
|
||||
@@ -123,15 +126,20 @@ func (s *SessionTracker) AddParticipant(ctx context.Context, p *types.Participan
|
||||
s.tracker.AddParticipant(*p)
|
||||
s.trackerCond.Broadcast()
|
||||
|
||||
err := s.service.UpdateSessionTracker(ctx, &proto.UpdateSessionTrackerRequest{
|
||||
SessionID: s.tracker.GetSessionID(),
|
||||
Update: &proto.UpdateSessionTrackerRequest_AddParticipant{
|
||||
AddParticipant: &proto.SessionTrackerAddParticipant{
|
||||
Participant: p,
|
||||
if s.service != nil {
|
||||
err := s.service.UpdateSessionTracker(ctx, &proto.UpdateSessionTrackerRequest{
|
||||
SessionID: s.tracker.GetSessionID(),
|
||||
Update: &proto.UpdateSessionTrackerRequest_AddParticipant{
|
||||
AddParticipant: &proto.SessionTrackerAddParticipant{
|
||||
Participant: p,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
return trace.Wrap(err)
|
||||
})
|
||||
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SessionTracker) RemoveParticipant(ctx context.Context, participantID string) error {
|
||||
@@ -140,15 +148,20 @@ func (s *SessionTracker) RemoveParticipant(ctx context.Context, participantID st
|
||||
s.tracker.RemoveParticipant(participantID)
|
||||
s.trackerCond.Broadcast()
|
||||
|
||||
err := s.service.UpdateSessionTracker(ctx, &proto.UpdateSessionTrackerRequest{
|
||||
SessionID: s.tracker.GetSessionID(),
|
||||
Update: &proto.UpdateSessionTrackerRequest_RemoveParticipant{
|
||||
RemoveParticipant: &proto.SessionTrackerRemoveParticipant{
|
||||
ParticipantID: participantID,
|
||||
if s.service != nil {
|
||||
err := s.service.UpdateSessionTracker(ctx, &proto.UpdateSessionTrackerRequest{
|
||||
SessionID: s.tracker.GetSessionID(),
|
||||
Update: &proto.UpdateSessionTrackerRequest_RemoveParticipant{
|
||||
RemoveParticipant: &proto.SessionTrackerRemoveParticipant{
|
||||
ParticipantID: participantID,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
return trace.Wrap(err)
|
||||
})
|
||||
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SessionTracker) UpdateState(ctx context.Context, state types.SessionState) error {
|
||||
@@ -157,15 +170,20 @@ func (s *SessionTracker) UpdateState(ctx context.Context, state types.SessionSta
|
||||
s.tracker.SetState(state)
|
||||
s.trackerCond.Broadcast()
|
||||
|
||||
err := s.service.UpdateSessionTracker(ctx, &proto.UpdateSessionTrackerRequest{
|
||||
SessionID: s.tracker.GetSessionID(),
|
||||
Update: &proto.UpdateSessionTrackerRequest_UpdateState{
|
||||
UpdateState: &proto.SessionTrackerUpdateState{
|
||||
State: state,
|
||||
if s.service != nil {
|
||||
err := s.service.UpdateSessionTracker(ctx, &proto.UpdateSessionTrackerRequest{
|
||||
SessionID: s.tracker.GetSessionID(),
|
||||
Update: &proto.UpdateSessionTrackerRequest_UpdateState{
|
||||
UpdateState: &proto.SessionTrackerUpdateState{
|
||||
State: state,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
return trace.Wrap(err)
|
||||
})
|
||||
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// WaitForStateUpdate waits for the tracker's state to be updated and returns the new state.
|
||||
|
||||
@@ -234,7 +234,6 @@ func newWebSuite(t *testing.T) *WebSuite {
|
||||
regular.SetUUID(nodeID),
|
||||
regular.SetNamespace(apidefaults.Namespace),
|
||||
regular.SetShell("/bin/sh"),
|
||||
regular.SetSessionServer(nodeClient),
|
||||
regular.SetEmitter(nodeClient),
|
||||
regular.SetPAMConfig(&pam.Config{Enabled: false}),
|
||||
regular.SetBPF(&bpf.NOP{}),
|
||||
@@ -319,7 +318,6 @@ func newWebSuite(t *testing.T) *WebSuite {
|
||||
s.proxyClient,
|
||||
regular.SetUUID(proxyID),
|
||||
regular.SetProxyMode("", revTunServer, s.proxyClient),
|
||||
regular.SetSessionServer(s.proxyClient),
|
||||
regular.SetEmitter(s.proxyClient),
|
||||
regular.SetNamespace(apidefaults.Namespace),
|
||||
regular.SetBPF(&bpf.NOP{}),
|
||||
@@ -3991,7 +3989,6 @@ func newWebPack(t *testing.T, numProxies int) *webPack {
|
||||
regular.SetUUID(nodeID),
|
||||
regular.SetNamespace(apidefaults.Namespace),
|
||||
regular.SetShell("/bin/sh"),
|
||||
regular.SetSessionServer(nodeClient),
|
||||
regular.SetEmitter(nodeClient),
|
||||
regular.SetPAMConfig(&pam.Config{Enabled: false}),
|
||||
regular.SetBPF(&bpf.NOP{}),
|
||||
@@ -4107,7 +4104,6 @@ func createProxy(ctx context.Context, t *testing.T, proxyID string, node *regula
|
||||
client,
|
||||
regular.SetUUID(proxyID),
|
||||
regular.SetProxyMode("", revTunServer, client),
|
||||
regular.SetSessionServer(client),
|
||||
regular.SetEmitter(client),
|
||||
regular.SetNamespace(apidefaults.Namespace),
|
||||
regular.SetBPF(&bpf.NOP{}),
|
||||
|
||||
+1
-1
Submodule webassets updated: 065c868116...614306f109
Reference in New Issue
Block a user