Files
teleport/lib/auth/session_access_test.go
T
0db64bbabe Fix Moderated session on leave pause action. (#21366)
* Fix Moderated session on leave pause action.

* Unpause when moderator rejoins session with onLeave=pause

This commite fixes a case when the moderator rejoins a paused session
but Teleport didn't resume the operations and the session was still
locked to everyone.

* Fix panic when re-joining SSH session.

* Update lib/kube/proxy/sess.go

Co-authored-by: Noah Stride <noah.stride@goteleport.com>

* Update lib/kube/proxy/sess.go

Co-authored-by: Noah Stride <noah.stride@goteleport.com>

* Fix typo

Co-authored-by: Tiago Silva <tiago.silva@goteleport.com>

---------

Co-authored-by: Tiago Silva <tiago.silva@goteleport.com>
Co-authored-by: Noah Stride <noah.stride@goteleport.com>
2023-02-17 14:11:59 +00:00

514 lines
15 KiB
Go

/*
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 (
"testing"
"github.com/stretchr/testify/require"
"github.com/gravitational/teleport/api/types"
)
type startTestCase struct {
name string
host []types.Role
sessionKinds []types.SessionKind
participants []SessionAccessContext
owner string
expected []bool
terminate types.OnSessionLeaveAction
}
func successStartTestCase(t *testing.T) startTestCase {
hostRole, err := types.NewRole("host", types.RoleSpecV6{})
require.NoError(t, err)
participantRole, err := types.NewRole("participant", types.RoleSpecV6{})
require.NoError(t, err)
hostRole.SetSessionRequirePolicies([]*types.SessionRequirePolicy{{
Filter: "contains(user.roles, \"participant\")",
Kinds: []string{string(types.SSHSessionKind), string(types.KubernetesSessionKind)},
Count: 2,
OnLeave: string(types.OnSessionLeaveTerminate),
Modes: []string{"peer"},
}})
participantRole.SetSessionJoinPolicies([]*types.SessionJoinPolicy{{
Roles: []string{hostRole.GetName()},
Kinds: []string{string(types.SSHSessionKind), string(types.KubernetesSessionKind)},
Modes: []string{"*"},
}})
return startTestCase{
name: "success",
host: []types.Role{hostRole},
sessionKinds: []types.SessionKind{types.SSHSessionKind, types.KubernetesSessionKind},
participants: []SessionAccessContext{
{
Username: "participant",
Roles: []types.Role{participantRole},
Mode: "peer",
},
{
Username: "participant2",
Roles: []types.Role{participantRole},
Mode: "peer",
},
},
expected: []bool{true, true},
terminate: types.OnSessionLeaveTerminate,
}
}
func successStartTestCasePause(t *testing.T) startTestCase {
hostRole, err := types.NewRole("host", types.RoleSpecV6{})
require.NoError(t, err)
participantRole, err := types.NewRole("participant", types.RoleSpecV6{})
require.NoError(t, err)
hostRole.SetSessionRequirePolicies([]*types.SessionRequirePolicy{{
Filter: "contains(user.roles, \"participant\")",
Kinds: []string{string(types.SSHSessionKind), string(types.KubernetesSessionKind)},
Count: 2,
OnLeave: string(types.OnSessionLeavePause),
Modes: []string{"peer"},
}})
participantRole.SetSessionJoinPolicies([]*types.SessionJoinPolicy{{
Roles: []string{hostRole.GetName()},
Kinds: []string{string(types.SSHSessionKind), string(types.KubernetesSessionKind)},
Modes: []string{"*"},
}})
return startTestCase{
name: "successStartTestCasePause",
host: []types.Role{hostRole},
sessionKinds: []types.SessionKind{types.SSHSessionKind, types.KubernetesSessionKind},
participants: []SessionAccessContext{
{
Username: "participant",
Roles: []types.Role{participantRole},
Mode: "peer",
},
{
Username: "participant2",
Roles: []types.Role{participantRole},
Mode: "peer",
},
},
expected: []bool{true, true},
terminate: types.OnSessionLeavePause,
}
}
func pauseCanBeOverwritten(t *testing.T) startTestCase {
hostRole, err := types.NewRole("host", types.RoleSpecV6{})
require.NoError(t, err)
participantRole, err := types.NewRole("participant", types.RoleSpecV6{})
require.NoError(t, err)
hostRole.SetSessionRequirePolicies([]*types.SessionRequirePolicy{
{
Filter: "contains(user.roles, \"participant\")",
Kinds: []string{string(types.SSHSessionKind), string(types.KubernetesSessionKind)},
Count: 2,
OnLeave: string(types.OnSessionLeavePause),
Modes: []string{"peer"},
},
{
Filter: "contains(user.roles, \"participant\")",
Kinds: []string{string(types.SSHSessionKind), string(types.KubernetesSessionKind)},
Count: 2,
OnLeave: string(types.OnSessionLeaveTerminate),
Modes: []string{"peer"},
},
})
participantRole.SetSessionJoinPolicies([]*types.SessionJoinPolicy{{
Roles: []string{hostRole.GetName()},
Kinds: []string{string(types.SSHSessionKind), string(types.KubernetesSessionKind)},
Modes: []string{"*"},
}})
return startTestCase{
name: "pauseCanBeOverwritten",
host: []types.Role{hostRole},
sessionKinds: []types.SessionKind{types.SSHSessionKind, types.KubernetesSessionKind},
participants: []SessionAccessContext{
{
Username: "participant",
Roles: []types.Role{participantRole},
Mode: "peer",
},
{
Username: "participant2",
Roles: []types.Role{participantRole},
Mode: "peer",
},
},
expected: []bool{true, true},
terminate: types.OnSessionLeaveTerminate,
}
}
func successStartTestCaseSpec(t *testing.T) startTestCase {
hostRole, err := types.NewRole("host", types.RoleSpecV6{})
require.NoError(t, err)
participantRole, err := types.NewRole("participant", types.RoleSpecV6{})
require.NoError(t, err)
hostRole.SetSessionRequirePolicies([]*types.SessionRequirePolicy{{
Filter: "contains(user.spec.roles, \"participant\")",
Kinds: []string{string(types.SSHSessionKind), string(types.KubernetesSessionKind)},
Count: 2,
OnLeave: string(types.OnSessionLeaveTerminate),
Modes: []string{"peer"},
}})
participantRole.SetSessionJoinPolicies([]*types.SessionJoinPolicy{{
Roles: []string{hostRole.GetName()},
Kinds: []string{string(types.SSHSessionKind), string(types.KubernetesSessionKind)},
Modes: []string{"*"},
}})
return startTestCase{
name: "success with spec",
host: []types.Role{hostRole},
sessionKinds: []types.SessionKind{types.SSHSessionKind, types.KubernetesSessionKind},
participants: []SessionAccessContext{
{
Username: "participant",
Roles: []types.Role{participantRole},
Mode: "peer",
},
{
Username: "participant2",
Roles: []types.Role{participantRole},
Mode: "peer",
},
},
expected: []bool{true, true},
terminate: types.OnSessionLeaveTerminate,
}
}
func failCountStartTestCase(t *testing.T) startTestCase {
hostRole, err := types.NewRole("host", types.RoleSpecV6{})
require.NoError(t, err)
participantRole, err := types.NewRole("participant", types.RoleSpecV6{})
require.NoError(t, err)
hostRole.SetSessionRequirePolicies([]*types.SessionRequirePolicy{{
Filter: "contains(user.roles, \"participant\")",
Kinds: []string{string(types.SSHSessionKind), string(types.KubernetesSessionKind)},
Count: 3,
Modes: []string{"peer"},
}})
participantRole.SetSessionJoinPolicies([]*types.SessionJoinPolicy{{
Roles: []string{hostRole.GetName()},
Kinds: []string{string(types.SSHSessionKind), string(types.KubernetesSessionKind)},
Modes: []string{"*"},
}})
return startTestCase{
name: "failCount",
host: []types.Role{hostRole},
sessionKinds: []types.SessionKind{types.SSHSessionKind, types.KubernetesSessionKind},
participants: []SessionAccessContext{
{
Username: "participant",
Roles: []types.Role{participantRole},
Mode: "peer",
},
{
Username: "participant2",
Roles: []types.Role{participantRole},
Mode: "peer",
},
},
expected: []bool{false, false},
terminate: types.OnSessionLeaveTerminate,
}
}
func succeedDiscardPolicySetStartTestCase(t *testing.T) startTestCase {
hostRole, err := types.NewRole("host", types.RoleSpecV6{})
require.NoError(t, err)
hostRole.SetSessionRequirePolicies([]*types.SessionRequirePolicy{{
Filter: "contains(user.roles, \"host\")",
Kinds: []string{string(types.KubernetesSessionKind)},
Count: 2,
Modes: []string{"peer"},
}})
return startTestCase{
name: "succeedDiscardPolicySet",
host: []types.Role{hostRole},
sessionKinds: []types.SessionKind{types.SSHSessionKind},
expected: []bool{true},
}
}
func failFilterStartTestCase(t *testing.T) startTestCase {
hostRole, err := types.NewRole("host", types.RoleSpecV6{})
require.NoError(t, err)
participantRole, err := types.NewRole("participant", types.RoleSpecV6{})
require.NoError(t, err)
hostRole.SetSessionRequirePolicies([]*types.SessionRequirePolicy{{
Filter: "contains(user.roles, \"host\")",
Kinds: []string{string(types.SSHSessionKind)},
Count: 2,
Modes: []string{"peer"},
}})
participantRole.SetSessionJoinPolicies([]*types.SessionJoinPolicy{{
Roles: []string{hostRole.GetName()},
Kinds: []string{string(types.SSHSessionKind)},
Modes: []string{"*"},
}})
return startTestCase{
name: "failFilter",
host: []types.Role{hostRole},
sessionKinds: []types.SessionKind{types.SSHSessionKind},
participants: []SessionAccessContext{
{
Username: "participant",
Roles: []types.Role{participantRole},
Mode: "peer",
},
{
Username: "participant2",
Roles: []types.Role{participantRole},
Mode: "peer",
},
},
expected: []bool{false},
terminate: types.OnSessionLeaveTerminate,
}
}
func TestSessionAccessStart(t *testing.T) {
t.Parallel()
testCases := []startTestCase{
successStartTestCase(t),
successStartTestCasePause(t),
successStartTestCaseSpec(t),
failCountStartTestCase(t),
failFilterStartTestCase(t),
succeedDiscardPolicySetStartTestCase(t),
pauseCanBeOverwritten(t),
}
for _, testCase := range testCases {
testCase := testCase
t.Run(testCase.name, func(t *testing.T) {
t.Parallel()
var policies []*types.SessionTrackerPolicySet
for _, role := range testCase.host {
policySet := role.GetSessionPolicySet()
policies = append(policies, &policySet)
}
for i, kind := range testCase.sessionKinds {
evaluator := NewSessionAccessEvaluator(policies, kind, testCase.owner)
result, policyOptions, err := evaluator.FulfilledFor(testCase.participants)
require.NoError(t, err)
require.Equal(t, testCase.expected[i], result)
require.Equal(t, testCase.terminate, policyOptions.OnLeaveAction)
}
})
}
}
type joinTestCase struct {
name string
host types.Role
sessionKinds []types.SessionKind
participant SessionAccessContext
owner string
expected []bool
}
func successJoinTestCase(t *testing.T) joinTestCase {
hostRole, err := types.NewRole("host", types.RoleSpecV6{})
require.NoError(t, err)
participantRole, err := types.NewRole("participant", types.RoleSpecV6{})
require.NoError(t, err)
participantRole.SetSessionJoinPolicies([]*types.SessionJoinPolicy{{
Roles: []string{hostRole.GetName()},
Kinds: []string{string(types.SSHSessionKind), string(types.KubernetesSessionKind)},
Modes: []string{types.Wildcard},
}})
return joinTestCase{
name: "success",
host: hostRole,
sessionKinds: []types.SessionKind{types.SSHSessionKind, types.KubernetesSessionKind},
participant: SessionAccessContext{
Username: "participant",
Roles: []types.Role{participantRole},
},
expected: []bool{true, true},
}
}
func successGlobJoinTestCase(t *testing.T) joinTestCase {
hostRole, err := types.NewRole("host", types.RoleSpecV6{})
require.NoError(t, err)
participantRole, err := types.NewRole("participant", types.RoleSpecV6{})
require.NoError(t, err)
participantRole.SetSessionJoinPolicies([]*types.SessionJoinPolicy{{
Roles: []string{types.Wildcard},
Kinds: []string{string(types.SSHSessionKind), string(types.KubernetesSessionKind)},
Modes: []string{types.Wildcard},
}})
return joinTestCase{
name: "successGlobJoin",
host: hostRole,
sessionKinds: []types.SessionKind{types.SSHSessionKind, types.KubernetesSessionKind},
participant: SessionAccessContext{
Username: "participant",
Roles: []types.Role{participantRole},
},
expected: []bool{true, true},
}
}
func successSameUserJoinTestCase(t *testing.T) joinTestCase {
hostRole, err := types.NewRole("host", types.RoleSpecV6{})
require.NoError(t, err)
participantRole, err := types.NewRole("participant", types.RoleSpecV6{})
require.NoError(t, err)
return joinTestCase{
name: "successSameUser",
host: hostRole,
sessionKinds: []types.SessionKind{types.SSHSessionKind},
participant: SessionAccessContext{
Username: "john",
Roles: []types.Role{participantRole},
},
owner: "john",
expected: []bool{true},
}
}
func failRoleJoinTestCase(t *testing.T) joinTestCase {
hostRole, err := types.NewRole("host", types.RoleSpecV6{})
require.NoError(t, err)
participantRole, err := types.NewRole("participant", types.RoleSpecV6{})
require.NoError(t, err)
return joinTestCase{
name: "failRole",
host: hostRole,
sessionKinds: []types.SessionKind{types.SSHSessionKind, types.KubernetesSessionKind},
participant: SessionAccessContext{
Username: "participant",
Roles: []types.Role{participantRole},
},
expected: []bool{false, false},
}
}
func failKindJoinTestCase(t *testing.T) joinTestCase {
hostRole, err := types.NewRole("host", types.RoleSpecV6{})
require.NoError(t, err)
participantRole, err := types.NewRole("participant", types.RoleSpecV6{})
require.NoError(t, err)
participantRole.SetSessionJoinPolicies([]*types.SessionJoinPolicy{{
Roles: []string{hostRole.GetName()},
Kinds: []string{string(types.KubernetesSessionKind)},
Modes: []string{types.Wildcard},
}})
return joinTestCase{
name: "failKind",
host: hostRole,
sessionKinds: []types.SessionKind{types.SSHSessionKind},
participant: SessionAccessContext{
Username: "participant",
Roles: []types.Role{participantRole},
},
expected: []bool{false},
}
}
func versionDefaultJoinTestCase(t *testing.T) joinTestCase {
hostRole, err := types.NewRole("host", types.RoleSpecV6{})
require.NoError(t, err)
// create a v3 role to check that access controls
// prior to Moderated Sessions are honored
participantRole := &types.RoleV6{
Version: types.V3,
Metadata: types.Metadata{
Name: "participant",
},
Spec: types.RoleSpecV6{},
}
require.NoError(t, participantRole.CheckAndSetDefaults())
return joinTestCase{
name: "failVersion",
host: hostRole,
sessionKinds: []types.SessionKind{types.SSHSessionKind, types.KubernetesSessionKind},
participant: SessionAccessContext{
Username: "participant",
Roles: []types.Role{participantRole},
},
expected: []bool{true, false},
}
}
func TestSessionAccessJoin(t *testing.T) {
t.Parallel()
testCases := []joinTestCase{
successJoinTestCase(t),
successGlobJoinTestCase(t),
successSameUserJoinTestCase(t),
failRoleJoinTestCase(t),
failKindJoinTestCase(t),
versionDefaultJoinTestCase(t),
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
for i, kind := range testCase.sessionKinds {
t.Run(string(kind), func(t *testing.T) {
policy := testCase.host.GetSessionPolicySet()
evaluator := NewSessionAccessEvaluator([]*types.SessionTrackerPolicySet{&policy}, kind, testCase.owner)
result := evaluator.CanJoin(testCase.participant)
require.Equal(t, testCase.expected[i], len(result) > 0)
})
}
})
}
}