Remove legacy session service (#15155)

This commit is contained in:
Joel
2022-08-12 16:39:45 +00:00
committed by GitHub
parent 51411cf5b9
commit f2dd75801a
24 changed files with 116 additions and 1268 deletions
+1 -1
Submodule e updated: fbb63ffef1...393cd15422
+25 -24
View File
@@ -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,
+2 -3
View File
@@ -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)
}
-1
View File
@@ -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"},
-83
View File
@@ -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"`
}
-244
View File
@@ -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))
})
}
})
}
+1 -71
View File
@@ -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
}
-4
View File
@@ -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,
}
-65
View File
@@ -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
+2 -2
View File
@@ -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 {
-2
View File
@@ -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
View File
@@ -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
View File
@@ -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),
-289
View File
@@ -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 {
-227
View File
@@ -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: &copy.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)
}
-3
View File
@@ -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
-8
View File
@@ -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) {
-6
View File
@@ -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"
+6 -41
View File
@@ -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 {
-5
View File
@@ -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
View File
@@ -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
View File
@@ -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.
-4
View File
@@ -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{}),