Files

214 lines
5.9 KiB
Go

/*
* Teleport
* Copyright (C) 2023 Gravitational, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package srv
import (
"errors"
"os"
"os/user"
"strconv"
"testing"
"github.com/stretchr/testify/require"
"golang.org/x/crypto/ssh"
decisionpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/decision/v1alpha1"
"github.com/gravitational/teleport/api/types"
apievents "github.com/gravitational/teleport/api/types/events"
"github.com/gravitational/teleport/lib/events"
"github.com/gravitational/teleport/lib/modules"
"github.com/gravitational/teleport/lib/sshutils"
"github.com/gravitational/teleport/lib/utils/log/logtest"
"github.com/gravitational/teleport/session/reexec"
"github.com/gravitational/teleport/session/reexec/reexecconstants"
)
// TestMain will re-execute Teleport to run a command if "exec" is passed to
// it as an argument. Otherwise, it will run tests as normal.
func TestMain(m *testing.M) {
reexec.MaybeReexec()
logtest.InitLogger(testing.Verbose)
modules.SetInsecureTestMode(true)
// Otherwise run tests as normal.
code := m.Run()
os.Exit(code)
}
// TestEmitExecAuditEvent make sure the full command and exit code for a
// command is always recorded.
func TestEmitExecAuditEvent(t *testing.T) {
t.Parallel()
srv := newMockServer(t)
scx := newExecServerContext(t, srv)
rec, ok := scx.party.s.recorder.(*mockRecorder)
require.True(t, ok)
expectedUsr, err := user.Current()
require.NoError(t, err)
expectedHostname := "testHost"
expectedMeta := apievents.UserMetadata{
User: "teleportUser",
Login: expectedUsr.Username,
Impersonator: "",
AWSRoleARN: "",
AccessRequests: []string(nil),
UserKind: apievents.UserKind_USER_KIND_HUMAN,
XXX_NoUnkeyedLiteral: struct{}{},
XXX_unrecognized: []uint8(nil),
XXX_sizecache: 0,
}
tests := []struct {
name string
inResult ExecResult
}{
{
name: "success",
inResult: ExecResult{
Command: "exit 0",
Error: nil,
Code: reexecconstants.RemoteCommandSuccess,
},
},
{
name: "exit with error",
inResult: ExecResult{
Command: "exit 255",
Error: errors.New("exit status 255"),
Code: reexecconstants.RemoteCommandFailure,
},
},
{
name: "command injection",
inResult: ExecResult{
Command: "/bin/teleport scp --remote-addr=127.0.0.1:50862 --local-addr=127.0.0.1:54895 -f ~/file.txt && touch /tmp/new.txt",
Error: errors.New("unknown error"),
Code: reexecconstants.RemoteCommandFailure,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
emitExecAuditEvent(scx, tt.inResult)
execEvent := rec.emitter.LastEvent().(*apievents.Exec)
require.Equal(t, tt.inResult.Command, execEvent.Command)
if tt.inResult.Error != nil {
require.Equal(t, tt.inResult.Error.Error(), execEvent.Error)
} else {
require.Empty(t, execEvent.Error)
}
if tt.inResult.Code == 0 {
require.Equal(t, events.ExecCode, execEvent.Code)
} else {
require.Equal(t, events.ExecFailureCode, execEvent.Code)
}
require.Equal(t, strconv.Itoa(tt.inResult.Code), execEvent.ExitCode)
require.Equal(t, expectedMeta, execEvent.UserMetadata)
require.Equal(t, "123", execEvent.ServerID)
require.Equal(t, "abc", execEvent.ForwardedBy)
require.Equal(t, expectedHostname, execEvent.ServerHostname)
require.Equal(t, "testNamespace", execEvent.ServerNamespace)
require.Equal(t, "xxx", execEvent.SessionID)
require.Equal(t, "10.0.0.5:4817", execEvent.RemoteAddr)
require.Equal(t, "127.0.0.1:3022", execEvent.LocalAddr)
require.NotEmpty(t, events.EventID)
})
}
}
func newExecServerContext(t *testing.T, srv Server) *ServerContext {
scx := newTestServerContext(t, srv, nil, nil)
term, err := newLocalTerminal(scx)
require.NoError(t, err)
term.SetTermType("xterm")
rec := &mockRecorder{done: false}
s := &session{
id: "xxx",
term: term,
emitter: rec,
recorder: rec,
scx: scx,
registry: &SessionRegistry{
SessionRegistryConfig: SessionRegistryConfig{
Srv: srv,
},
},
}
scx.party = newParty(s, types.SessionPeerMode, nil, scx)
err = scx.SetSSHRequest(&ssh.Request{Type: sshutils.ExecRequest})
require.NoError(t, err)
t.Cleanup(func() { require.NoError(t, scx.party.s.term.Close()) })
return scx
}
func TestCheckSCPAllowed(t *testing.T) {
t.Parallel()
tests := []struct {
name string
command string
assert require.BoolAssertionFunc
}{
{
name: "scp command",
command: "scp foo bar",
assert: require.True,
},
{
name: "other command",
command: "some other command",
assert: require.False,
},
{
name: "no command",
command: "",
assert: require.False,
},
{
name: "scp command with whitespace",
command: "\tscp foo bar",
assert: require.True,
},
{
name: "other bash commands aren't affected",
command: "echo $((1+1))",
assert: require.False,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
scx := newTestServerContext(t, nil, nil, nil)
scx.AllowFileCopying = true
scx.Identity.AccessPermit = decisionpb.SSHAccessPermit_builder{SshFileCopy: true}.Build()
ok, err := checkSCPAllowed(scx, tc.command)
require.NoError(t, err)
tc.assert(t, ok)
})
}
}