diff --git a/lib/events/api.go b/lib/events/api.go index b1dda9a2b6db7..055e060f57ce7 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 f8784a646a77e..b5050f5951fb2 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 492cdce87d7f5..fb017be8ddc9a 100644 --- a/lib/srv/ctx.go +++ b/lib/srv/ctx.go @@ -1269,19 +1269,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 849bc0a228c53..2b032cc0a6f22 100644 --- a/lib/srv/forward/sshserver.go +++ b/lib/srv/forward/sshserver.go @@ -941,7 +941,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.logger.ErrorContext(ctx, "Failed to emit audit event", "error", err) } @@ -1120,7 +1120,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 { s.logger.WarnContext(ctx, "Failed to emit port forward event", "error", err) } diff --git a/lib/srv/regular/sshserver.go b/lib/srv/regular/sshserver.go index d68b7367cbc8f..474986424d3fe 100644 --- a/lib/srv/regular/sshserver.go +++ b/lib/srv/regular/sshserver.go @@ -1489,27 +1489,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) { + errEvent := scx.GetPortForwardEvent(events.PortForwardLocalEvent, events.PortForwardFailureCode, scx.DstAddr) + s.emitAuditEventWithLog(ctx, &errEvent) scx.Logger.WarnContext(ctx, "Connection problem in direct-tcpip channel", "error", err) } - 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 { - scx.Logger.WarnContext(ctx, "Failed to emit port forward event", "error", err) - } + stopEvent := scx.GetPortForwardEvent(events.PortForwardLocalEvent, events.PortForwardStopCode, scx.DstAddr) + s.emitAuditEventWithLog(ctx, &stopEvent) } // handleSessionRequests handles out of band session requests once the session @@ -1868,9 +1858,7 @@ func (s *Server) handleX11Forward(ctx context.Context, ch ssh.Channel, req *ssh. s.replyError(ctx, ch, req, err) err = nil } - if err := s.EmitAuditEvent(s.ctx, event); err != nil { - scx.Logger.WarnContext(s.ctx, "Failed to emit x11-forward event", "error", err) - } + s.emitAuditEventWithLog(s.ctx, event) }() // check if X11 forwarding is disabled, or if xauth can't be handled. @@ -2162,6 +2150,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 @@ -2201,13 +2190,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.WarnContext(ctx, "Failed to emit audit event", "error", err) - } - 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 { @@ -2250,7 +2298,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) @@ -2258,6 +2305,7 @@ func (s *Server) handleCancelTCPIPForwardRequest(ctx context.Context, ccx *sshut if err := r.Reply(true, nil); err != nil { s.logger.WarnContext(ctx, "Failed to reply to request", "request_type", r.Type, "error", err) } + return trace.Wrap(listener.Close()) } @@ -2300,7 +2348,7 @@ func (s *Server) parseSubsystemRequest(ctx context.Context, req *ssh.Request, se case r.Name == teleport.SFTPSubsystem: err := serverContext.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, @@ -2347,3 +2395,9 @@ func (s *Server) handlePuTTYWinadj(ctx context.Context, 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 { + s.logger.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 a2212126416fb..2ae622fbfff40 100644 --- a/lib/srv/regular/sshserver_test.go +++ b/lib/srv/regular/sshserver_test.go @@ -474,8 +474,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) @@ -517,32 +515,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 a7308f010db7c..a2efc075ed746 100644 --- a/lib/sshutils/tcpip.go +++ b/lib/sshutils/tcpip.go @@ -20,15 +20,10 @@ package sshutils import ( "context" - "io" "log/slog" - "net" "github.com/gravitational/trace" "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 +67,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) { - slog.WarnContext(ctx, "failed to accept connection", "error", err) - } - return - } - logger := slog.With( - "src_addr", srcAddr, - "remote_addr", conn.RemoteAddr().String(), - ) - - dstHost, dstPort, err := SplitHostPort(conn.RemoteAddr().String()) - if err != nil { - conn.Close() - logger.WarnContext(ctx, "failed to parse addr", "error", err) - return - } - - req := ForwardedTCPIPRequest{ - Addr: srcHost, - Port: srcPort, - 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 := sshConn.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 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; }; };