From a88a277174dd258d38cbf56965f2d84774d9e288 Mon Sep 17 00:00:00 2001 From: Erik Tate Date: Wed, 18 Dec 2024 17:08:21 -0500 Subject: [PATCH] adding additional audit log context around SSH port forwarding --- lib/events/api.go | 11 +- lib/events/dynamic.go | 6 + lib/srv/ctx.go | 8 +- lib/srv/forward/sshserver.go | 4 +- lib/srv/regular/sshserver.go | 114 +++++++++++++----- lib/srv/regular/sshserver_test.go | 82 ++++++++++--- lib/sshutils/mock_test.go | 8 -- lib/sshutils/tcpip.go | 68 ----------- lib/sshutils/tcpip_test.go | 63 ---------- .../teleport/src/services/audit/makeEvent.ts | 89 ++++++++++++-- .../teleport/src/services/audit/types.ts | 2 +- 11 files changed, 248 insertions(+), 207 deletions(-) delete mode 100644 lib/sshutils/tcpip_test.go diff --git a/lib/events/api.go b/lib/events/api.go index c54bf49475e7b..774c518cefc2c 100644 --- a/lib/events/api.go +++ b/lib/events/api.go @@ -264,10 +264,13 @@ const ( X11ForwardErr = "error" // Port forwarding event - PortForwardEvent = "port" - PortForwardAddr = "addr" - PortForwardSuccess = "success" - PortForwardErr = "error" + PortForwardEvent = "port" + PortForwardLocalEvent = "port.local" + PortForwardRemoteEvent = "port.remote" + PortForwardRemoteConnEvent = "port.remote_conn" + PortForwardAddr = "addr" + PortForwardSuccess = "success" + PortForwardErr = "error" // AuthAttemptEvent is authentication attempt that either // succeeded or failed based on event status diff --git a/lib/events/dynamic.go b/lib/events/dynamic.go index a638abb99d25a..038946beb98c4 100644 --- a/lib/events/dynamic.go +++ b/lib/events/dynamic.go @@ -107,6 +107,12 @@ func FromEventFields(fields EventFields) (events.AuditEvent, error) { e = &events.X11Forward{} case PortForwardEvent: e = &events.PortForward{} + case PortForwardLocalEvent: + e = &events.PortForward{} + case PortForwardRemoteEvent: + e = &events.PortForward{} + case PortForwardRemoteConnEvent: + e = &events.PortForward{} case AuthAttemptEvent: e = &events.AuthAttempt{} case SCPEvent: diff --git a/lib/srv/ctx.go b/lib/srv/ctx.go index 3e5d290ea61e8..f5db5d312e96d 100644 --- a/lib/srv/ctx.go +++ b/lib/srv/ctx.go @@ -1263,19 +1263,19 @@ func (c *ServerContext) GetSessionMetadata() apievents.SessionMetadata { } } -func (c *ServerContext) GetPortForwardEvent() apievents.PortForward { +func (c *ServerContext) GetPortForwardEvent(evType, code, addr string) apievents.PortForward { sconn := c.ConnectionContext.ServerConn return apievents.PortForward{ Metadata: apievents.Metadata{ - Type: events.PortForwardEvent, - Code: events.PortForwardCode, + Type: evType, + Code: code, }, UserMetadata: c.Identity.GetUserMetadata(), ConnectionMetadata: apievents.ConnectionMetadata{ LocalAddr: sconn.LocalAddr().String(), RemoteAddr: sconn.RemoteAddr().String(), }, - Addr: c.DstAddr, + Addr: addr, Status: apievents.Status{ Success: true, }, diff --git a/lib/srv/forward/sshserver.go b/lib/srv/forward/sshserver.go index bdc4cd69dc620..7bba9339436c3 100644 --- a/lib/srv/forward/sshserver.go +++ b/lib/srv/forward/sshserver.go @@ -942,7 +942,7 @@ func (s *Server) handleForwardedTCPIPRequest(ctx context.Context, nch ssh.NewCha go io.Copy(io.Discard, ch.Stderr()) ch = scx.TrackActivity(ch) - event := scx.GetPortForwardEvent() + event := scx.GetPortForwardEvent(events.PortForwardEvent, events.PortForwardCode, scx.DstAddr) if err := s.EmitAuditEvent(ctx, &event); err != nil { s.log.WithError(err).Error("Failed to emit audit event.") } @@ -1121,7 +1121,7 @@ func (s *Server) handleDirectTCPIPRequest(ctx context.Context, ch ssh.Channel, r } defer conn.Close() - event := scx.GetPortForwardEvent() + event := scx.GetPortForwardEvent(events.PortForwardEvent, events.PortForwardFailureCode, scx.DstAddr) if err := s.EmitAuditEvent(s.closeContext, &event); err != nil { scx.WithError(err).Warn("Failed to emit port forward event.") } diff --git a/lib/srv/regular/sshserver.go b/lib/srv/regular/sshserver.go index de400ac756989..63570f0a1d6b7 100644 --- a/lib/srv/regular/sshserver.go +++ b/lib/srv/regular/sshserver.go @@ -1496,27 +1496,17 @@ func (s *Server) handleDirectTCPIPRequest(ctx context.Context, ccx *sshutils.Con return } + startEvent := scx.GetPortForwardEvent(events.PortForwardLocalEvent, events.PortForwardCode, scx.DstAddr) + s.emitAuditEventWithLog(ctx, &startEvent) + if err := utils.ProxyConn(ctx, conn, channel); err != nil && !errors.Is(err, io.EOF) && !errors.Is(err, os.ErrClosed) { - s.Logger.Warnf("Connection problem in direct-tcpip channel: %v %T.", trace.DebugReport(err), err) + errEvent := scx.GetPortForwardEvent(events.PortForwardLocalEvent, events.PortForwardFailureCode, scx.DstAddr) + s.emitAuditEventWithLog(ctx, &errEvent) + scx.Logger.WithError(err).Warn("Connection problem in direct-tcpip channel") } - if err := s.EmitAuditEvent(s.ctx, &apievents.PortForward{ - Metadata: apievents.Metadata{ - Type: events.PortForwardEvent, - Code: events.PortForwardCode, - }, - UserMetadata: scx.Identity.GetUserMetadata(), - ConnectionMetadata: apievents.ConnectionMetadata{ - LocalAddr: scx.ServerConn.LocalAddr().String(), - RemoteAddr: scx.ServerConn.RemoteAddr().String(), - }, - Addr: scx.DstAddr, - Status: apievents.Status{ - Success: true, - }, - }); err != nil { - s.Logger.WithError(err).Warn("Failed to emit port forward event.") - } + stopEvent := scx.GetPortForwardEvent(events.PortForwardLocalEvent, events.PortForwardStopCode, scx.DstAddr) + s.emitAuditEventWithLog(ctx, &stopEvent) } // handleSessionRequests handles out of band session requests once the session @@ -1876,9 +1866,7 @@ func (s *Server) handleX11Forward(ch ssh.Channel, req *ssh.Request, scx *srv.Ser s.replyError(ch, req, err) err = nil } - if err := s.EmitAuditEvent(s.ctx, event); err != nil { - s.Logger.WithError(err).Warn("Failed to emit x11-forward event.") - } + s.emitAuditEventWithLog(s.ctx, event) }() // check if X11 forwarding is disabled, or if xauth can't be handled. @@ -2170,6 +2158,7 @@ func (s *Server) createForwardingContext(ctx context.Context, ccx *sshutils.Conn if err != nil { return nil, nil, trace.Wrap(err) } + listenAddr := sshutils.JoinHostPort(req.Addr, req.Port) scx.IsTestStub = s.isTestStub scx.ExecType = teleport.TCPIPForwardRequest @@ -2209,13 +2198,72 @@ func (s *Server) handleTCPIPForwardRequest(ctx context.Context, ccx *sshutils.Co } scx.SrcAddr = sshutils.JoinHostPort(srcHost, listenPort) - event := scx.GetPortForwardEvent() - if err := s.EmitAuditEvent(ctx, &event); err != nil { - s.Logger.WithError(err).Warn("Failed to emit audit event.") - } - if err := sshutils.StartRemoteListener(ctx, scx.ConnectionContext.ServerConn, scx.SrcAddr, listener); err != nil { - return trace.Wrap(err) - } + event := scx.GetPortForwardEvent(events.PortForwardRemoteEvent, events.PortForwardCode, scx.SrcAddr) + s.emitAuditEventWithLog(ctx, &event) + + // spawn remote forwarding handler to multiplex connections to the forwarded port + go func() { + stopEvent := scx.GetPortForwardEvent(events.PortForwardRemoteEvent, events.PortForwardStopCode, scx.SrcAddr) + defer s.emitAuditEventWithLog(ctx, &stopEvent) + + for { + conn, err := listener.Accept() + if err != nil { + if !utils.IsOKNetworkError(err) { + slog.WarnContext(ctx, "failed to accept connection", "error", err) + } + return + } + logger := slog.With( + "src_addr", scx.SrcAddr, + "remote_addr", conn.RemoteAddr().String(), + ) + + dstHost, dstPort, err := sshutils.SplitHostPort(conn.RemoteAddr().String()) + if err != nil { + conn.Close() + logger.WarnContext(ctx, "failed to parse addr", "error", err) + return + } + + req := sshutils.ForwardedTCPIPRequest{ + Addr: srcHost, + Port: listenPort, + OrigAddr: dstHost, + OrigPort: dstPort, + } + if err := req.CheckAndSetDefaults(); err != nil { + conn.Close() + logger.WarnContext(ctx, "failed to create forwarded tcpip request", "error", err) + return + } + reqBytes := ssh.Marshal(req) + + ch, rch, err := scx.ConnectionContext.ServerConn.OpenChannel(teleport.ChanForwardedTCPIP, reqBytes) + if err != nil { + conn.Close() + logger.WarnContext(ctx, "failed to open channel", "error", err) + continue + } + go ssh.DiscardRequests(rch) + go io.Copy(io.Discard, ch.Stderr()) + go func() { + startEvent := scx.GetPortForwardEvent(events.PortForwardRemoteConnEvent, events.PortForwardCode, scx.SrcAddr) + startEvent.RemoteAddr = conn.RemoteAddr().String() + s.emitAuditEventWithLog(ctx, &startEvent) + + if err := utils.ProxyConn(ctx, conn, ch); err != nil { + errEvent := scx.GetPortForwardEvent(events.PortForwardRemoteConnEvent, events.PortForwardFailureCode, scx.SrcAddr) + errEvent.RemoteAddr = conn.RemoteAddr().String() + s.emitAuditEventWithLog(ctx, &errEvent) + } + + stopEvent := scx.GetPortForwardEvent(events.PortForwardRemoteConnEvent, events.PortForwardStopCode, scx.SrcAddr) + stopEvent.RemoteAddr = conn.RemoteAddr().String() + s.emitAuditEventWithLog(ctx, &stopEvent) + }() + } + }() // Report addr back to the client. if r.WantReply { @@ -2258,7 +2306,6 @@ func (s *Server) handleCancelTCPIPForwardRequest(ctx context.Context, ccx *sshut return trace.Wrap(err) } defer scx.Close() - listener, ok := s.remoteForwardingMap.LoadAndDelete(scx.SrcAddr) if !ok { return trace.NotFound("no remote forwarding listener at %v", scx.SrcAddr) @@ -2266,6 +2313,7 @@ func (s *Server) handleCancelTCPIPForwardRequest(ctx context.Context, ccx *sshut if err := r.Reply(true, nil); err != nil { s.Logger.Warnf("Failed to reply to %q request: %v", r.Type, err) } + return trace.Wrap(listener.Close()) } @@ -2308,7 +2356,7 @@ func (s *Server) parseSubsystemRequest(req *ssh.Request, ctx *srv.ServerContext) case r.Name == teleport.SFTPSubsystem: err := ctx.CheckSFTPAllowed(s.reg) if err != nil { - s.EmitAuditEvent(context.Background(), &apievents.SFTP{ + s.emitAuditEventWithLog(context.Background(), &apievents.SFTP{ Metadata: apievents.Metadata{ Code: events.SFTPDisallowedCode, Type: events.SFTPEvent, @@ -2355,3 +2403,9 @@ func (s *Server) handlePuTTYWinadj(ch ssh.Channel, req *ssh.Request) error { req.WantReply = false return nil } + +func (s *Server) emitAuditEventWithLog(ctx context.Context, event apievents.AuditEvent) { + if err := s.EmitAuditEvent(ctx, event); err != nil { + slog.WarnContext(ctx, "Failed to emit event", "type", event.GetType(), "code", event.GetCode()) + } +} diff --git a/lib/srv/regular/sshserver_test.go b/lib/srv/regular/sshserver_test.go index 47e688e9fa7d1..1ae4dd4203b83 100644 --- a/lib/srv/regular/sshserver_test.go +++ b/lib/srv/regular/sshserver_test.go @@ -475,8 +475,6 @@ func TestSessionAuditLog(t *testing.T) { roleOptions := role.GetOptions() roleOptions.PermitX11Forwarding = types.NewBool(true) roleOptions.ForwardAgent = types.NewBool(true) - //nolint:staticcheck // this field is preserved for existing deployments, but shouldn't be used going forward - roleOptions.PortForwarding = types.NewBoolOption(true) role.SetOptions(roleOptions) _, err = f.testSrv.Auth().UpsertRole(ctx, role) require.NoError(t, err) @@ -518,32 +516,82 @@ func TestSessionAuditLog(t *testing.T) { x11Event := nextEvent() require.IsType(t, &apievents.X11Forward{}, x11Event, "expected X11Forward event but got event of tgsype %T", x11Event) - // Request a remote port forwarding listener. + // LOCAL PORT FORWARDING + // Start up a test server that doesn't do any remote port forwarding + nonForwardServer := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintln(w, "hello, world") + })) + t.Cleanup(nonForwardServer.Close) + nonForwardServer.Start() + + // Each locally forwarded dial should result in a new "start" event and each closed connection should result in a "stop" + // event. Note that we don't know what port the server will forward the connection on, so we don't have an easy way to validate the + // event's addr field. + localConn, err := f.ssh.clt.DialContext(context.Background(), "tcp", nonForwardServer.Listener.Addr().String()) + require.NoError(t, err) + + e = nextEvent() + localForwardStart, ok := e.(*apievents.PortForward) + require.True(t, ok, "expected PortForward event but got event of type %T", e) + require.Equal(t, events.PortForwardLocalEvent, localForwardStart.GetType()) + require.Equal(t, events.PortForwardCode, localForwardStart.GetCode()) + require.Equal(t, nonForwardServer.Listener.Addr().String(), localForwardStart.Addr) + + // closed connections should result in PortForwardLocal stop events + localConn.Close() + e = nextEvent() + localForwardStop, ok := e.(*apievents.PortForward) + require.True(t, ok, "expected PortForward event but got event of type %T", e) + require.Equal(t, events.PortForwardLocalEvent, localForwardStop.GetType()) + require.Equal(t, events.PortForwardStopCode, localForwardStop.GetCode()) + require.Equal(t, nonForwardServer.Listener.Addr().String(), localForwardStop.Addr) + + // REMOTE PORT FORWARDING + // Creation of a port forwarded listener should generate PortForwardRemote start events listener, err := f.ssh.clt.Listen("tcp", "127.0.0.1:0") require.NoError(t, err) - // Start up a test server that uses the port forwarded listener. - ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + e = nextEvent() + remoteForwardStart, ok := e.(*apievents.PortForward) + require.True(t, ok, "expected PortForward event but got event of type %T", e) + require.Equal(t, listener.Addr().String(), remoteForwardStart.Addr) + require.Equal(t, events.PortForwardRemoteEvent, remoteForwardStart.GetType()) + require.Equal(t, events.PortForwardCode, remoteForwardStart.GetCode()) + + // Start up a test server that uses the remote port forwarded listener. + remoteForwardServer := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "hello, world") })) - t.Cleanup(ts.Close) - ts.Listener = listener - ts.Start() + t.Cleanup(remoteForwardServer.Close) + remoteForwardServer.Listener = listener + remoteForwardServer.Start() - // Request forward to remote port. Each dial should result in a new event. Note that we don't - // know what port the server will forward the connection on, so we don't have an easy way to - // validate the event's addr field. - conn, err := f.ssh.clt.DialContext(context.Background(), "tcp", listener.Addr().String()) + // Each dial to the remote listener should result in a new "start" event and each closed connection should result in a "stop" event. + // Note that we don't know what port the server will forward the connection on, so we don't have an easy way to validate the event's + // addr field. + remoteConn, err := net.Dial("tcp", listener.Addr().String()) require.NoError(t, err) - conn.Close() + e = nextEvent() + remoteConnStart, ok := e.(*apievents.PortForward) + require.True(t, ok, "expected PortForward event but got event of type %T", e) + require.Equal(t, events.PortForwardRemoteConnEvent, remoteConnStart.GetType()) + require.Equal(t, events.PortForwardCode, remoteConnStart.GetCode()) - directPortForwardEvent := nextEvent() - require.IsType(t, &apievents.PortForward{}, directPortForwardEvent, "expected PortForward event but got event of type %T", directPortForwardEvent) + remoteConn.Close() + e = nextEvent() + remoteConnStop, ok := e.(*apievents.PortForward) + require.True(t, ok, "expected PortForward event but got event of type %T", e) + require.Equal(t, events.PortForwardRemoteConnEvent, remoteConnStop.GetType()) + require.Equal(t, events.PortForwardStopCode, remoteConnStop.GetCode()) + // Closing the server (and therefore the listener) should generate an PortForwardRemote stop event + remoteForwardServer.Close() e = nextEvent() - remotePortForwardEvent, ok := e.(*apievents.PortForward) + remoteForwardStop, ok := e.(*apievents.PortForward) require.True(t, ok, "expected PortForward event but got event of type %T", e) - require.Equal(t, listener.Addr().String(), remotePortForwardEvent.Addr) + require.Equal(t, events.PortForwardRemoteEvent, remoteForwardStop.GetType()) + require.Equal(t, events.PortForwardStopCode, remoteForwardStop.Code) + require.Equal(t, listener.Addr().String(), remoteForwardStop.Addr) // End the session. Session leave, data, and end events should be emitted. se.Close() diff --git a/lib/sshutils/mock_test.go b/lib/sshutils/mock_test.go index 43cfe4c543eb9..d01230bea5002 100644 --- a/lib/sshutils/mock_test.go +++ b/lib/sshutils/mock_test.go @@ -61,14 +61,6 @@ func (mc *mockChannel) Stderr() io.ReadWriter { return fakeReaderWriter{} } -type mockSSHConn struct { - mockChan *mockChannel -} - -func (mc *mockSSHConn) OpenChannel(name string, data []byte) (ssh.Channel, <-chan *ssh.Request, error) { - return mc.mockChan, make(<-chan *ssh.Request), nil -} - type mockSSHNewChannel struct { mock.Mock ssh.NewChannel diff --git a/lib/sshutils/tcpip.go b/lib/sshutils/tcpip.go index 55ac4ea981cf3..2dcdd4e520da9 100644 --- a/lib/sshutils/tcpip.go +++ b/lib/sshutils/tcpip.go @@ -19,16 +19,9 @@ package sshutils import ( - "context" - "io" - "net" - "github.com/gravitational/trace" log "github.com/sirupsen/logrus" "golang.org/x/crypto/ssh" - - "github.com/gravitational/teleport" - "github.com/gravitational/teleport/lib/utils" ) // DirectTCPIPReq represents the payload of an SSH "direct-tcpip" or @@ -72,64 +65,3 @@ func ParseTCPIPForwardReq(data []byte) (*TCPIPForwardReq, error) { } return &r, nil } - -type channelOpener interface { - OpenChannel(name string, data []byte) (ssh.Channel, <-chan *ssh.Request, error) -} - -// StartRemoteListener listens on the given listener and forwards any accepted -// connections over a new "forwarded-tcpip" channel. -func StartRemoteListener(ctx context.Context, sshConn channelOpener, srcAddr string, listener net.Listener) error { - srcHost, srcPort, err := SplitHostPort(srcAddr) - if err != nil { - return trace.Wrap(err) - } - - go func() { - for { - conn, err := listener.Accept() - if err != nil { - if !utils.IsOKNetworkError(err) { - log.WithError(err).Warn("failed to accept connection") - } - return - } - logger := log.WithFields(log.Fields{ - "srcAddr": srcAddr, - "remoteAddr": conn.RemoteAddr().String(), - }) - - dstHost, dstPort, err := SplitHostPort(conn.RemoteAddr().String()) - if err != nil { - conn.Close() - logger.WithError(err).Warn("failed to parse addr") - return - } - - req := ForwardedTCPIPRequest{ - Addr: srcHost, - Port: srcPort, - OrigAddr: dstHost, - OrigPort: dstPort, - } - if err := req.CheckAndSetDefaults(); err != nil { - conn.Close() - logger.WithError(err).Warn("failed to create forwarded tcpip request") - return - } - reqBytes := ssh.Marshal(req) - - ch, rch, err := sshConn.OpenChannel(teleport.ChanForwardedTCPIP, reqBytes) - if err != nil { - conn.Close() - logger.WithError(err).Warn("failed to open channel") - continue - } - go ssh.DiscardRequests(rch) - go io.Copy(io.Discard, ch.Stderr()) - go utils.ProxyConn(ctx, conn, ch) - } - }() - - return nil -} diff --git a/lib/sshutils/tcpip_test.go b/lib/sshutils/tcpip_test.go deleted file mode 100644 index 5a59b5a64e57f..0000000000000 --- a/lib/sshutils/tcpip_test.go +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Teleport - * Copyright (C) 2024 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 . - */ - -package sshutils - -import ( - "context" - "fmt" - "io" - "net" - "net/http" - "net/http/httptest" - "testing" - "time" - - "github.com/stretchr/testify/require" -) - -func TestStartRemoteListener(t *testing.T) { - // Create a test server to act as the other side of the channel. - tsrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - fmt.Fprint(w, "Hello, world") - })) - t.Cleanup(tsrv.Close) - testSrvConn, err := net.Dial("tcp", tsrv.Listener.Addr().String()) - require.NoError(t, err) - - sshConn := &mockSSHConn{ - mockChan: &mockChannel{ - ReadWriter: testSrvConn, - }, - } - - // Start the remote listener. - listener, err := net.Listen("tcp", "127.0.0.1:0") - require.NoError(t, err) - ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) - t.Cleanup(cancel) - require.NoError(t, StartRemoteListener(ctx, sshConn, "127.0.0.1:12345", listener)) - - // Check that dialing listener makes it all the way to the test http server. - resp, err := http.Get("http://" + listener.Addr().String()) - require.NoError(t, err) - defer resp.Body.Close() - body, err := io.ReadAll(resp.Body) - require.NoError(t, err) - require.Equal(t, "Hello, world", string(body)) -} diff --git a/web/packages/teleport/src/services/audit/makeEvent.ts b/web/packages/teleport/src/services/audit/makeEvent.ts index 97675560e94af..308bcea97106a 100644 --- a/web/packages/teleport/src/services/audit/makeEvent.ts +++ b/web/packages/teleport/src/services/audit/makeEvent.ts @@ -20,7 +20,14 @@ import { formatDistanceStrict } from 'date-fns'; import { pluralize } from 'shared/utils/text'; -import { Event, eventCodes, Formatters, RawEvent, RawEvents } from './types'; +import { + Event, + EventCode, + eventCodes, + Formatters, + RawEvent, + RawEvents, +} from './types'; const formatElasticsearchEvent: ( json: @@ -65,6 +72,66 @@ const formatElasticsearchEvent: ( return message; }; +const portForwardEventTypes = [ + 'port', + 'port.local', + 'port.remote', + 'port.remote_conn', +] as const; +type PortForwardEventType = (typeof portForwardEventTypes)[number]; +type PortForwardEvent = + | RawEvents[typeof eventCodes.PORTFORWARD] + | RawEvents[typeof eventCodes.PORTFORWARD_STOP] + | RawEvents[typeof eventCodes.PORTFORWARD_FAILURE]; + +const getPortForwardEventName = (event: string): string => { + let ev = event as PortForwardEventType; + if (!portForwardEventTypes.includes(ev)) { + ev = 'port'; // default to generic 'port' if the event type is unknown + } + + switch (ev) { + case 'port': + return 'Port Forwarding'; + case 'port.local': + return 'Local Port Forwarding'; + case 'port.remote': + return 'Remote Port Forwarding'; + case 'port.remote_conn': + return 'Remote Port Forwarded Connection'; + } +}; + +const formatPortForwardEvent = ({ + user, + code, + event, +}: PortForwardEvent): string => { + const eventName = getPortForwardEventName(event).toLowerCase(); + + switch (code) { + case eventCodes.PORTFORWARD: + return `User [${user}] started ${eventName}`; + case eventCodes.PORTFORWARD_STOP: + return `User [${user}] stopped ${eventName}`; + case eventCodes.PORTFORWARD_FAILURE: + return `User [${user}] failed ${eventName}`; + } +}; + +const describePortForwardEvent = ({ code, event }: PortForwardEvent) => { + const eventName = getPortForwardEventName(event); + + switch (code) { + case eventCodes.PORTFORWARD: + return `${eventName} Start`; + case eventCodes.PORTFORWARD_STOP: + return `${eventName} Stop`; + case eventCodes.PORTFORWARD_FAILURE: + return `${eventName} Failure`; + } +}; + export const formatters: Formatters = { [eventCodes.ACCESS_REQUEST_CREATED]: { type: 'access_request.create', @@ -223,19 +290,18 @@ export const formatters: Formatters = { }, [eventCodes.PORTFORWARD]: { type: 'port', - desc: 'Port Forwarding Started', - format: ({ user }) => `User [${user}] started port forwarding`, + desc: describePortForwardEvent, + format: formatPortForwardEvent, }, [eventCodes.PORTFORWARD_FAILURE]: { type: 'port', - desc: 'Port Forwarding Failed', - format: ({ user, error }) => - `User [${user}] port forwarding request failed: ${error}`, + desc: describePortForwardEvent, + format: formatPortForwardEvent, }, [eventCodes.PORTFORWARD_STOP]: { type: 'port', - desc: 'Port Forwarding Stopped', - format: ({ user }) => `User [${user}] stopped port forwarding`, + desc: describePortForwardEvent, + format: formatPortForwardEvent, }, [eventCodes.SAML_CONNECTOR_CREATED]: { type: 'saml.created', @@ -1965,9 +2031,12 @@ const unknownFormatter = { export default function makeEvent(json: any): Event { // lookup event formatter by code - const formatter = formatters[json.code] || unknownFormatter; + const formatter = formatters[json.code as EventCode] || unknownFormatter; return { - codeDesc: formatter.desc, + codeDesc: + typeof formatter.desc === 'function' + ? formatter.desc(json) + : formatter.desc, message: formatter.format(json as any), id: getId(json), code: json.code, diff --git a/web/packages/teleport/src/services/audit/types.ts b/web/packages/teleport/src/services/audit/types.ts index 26c94f54e0b51..cbcb7b8015482 100644 --- a/web/packages/teleport/src/services/audit/types.ts +++ b/web/packages/teleport/src/services/audit/types.ts @@ -1991,7 +1991,7 @@ type RawSpannerRPCEvent = RawEvent< export type Formatters = { [key in EventCode]: { type: string; - desc: string; + desc: string | ((json: RawEvents[key]) => string); format: (json: RawEvents[key]) => string; }; };