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;
};
};