Files
teleport/lib/events/emitter.go
T
Zac Bergquist 166dc5a1be Clean up defaults (#18860)
- Make vars const where possible
- Move constants that are only used by the audit log into lib/events
- Remove unused vars
2022-11-30 15:50:29 +00:00

691 lines
20 KiB
Go

/*
Copyright 2020 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 events
import (
"context"
"fmt"
"io"
"time"
"github.com/gravitational/trace"
"github.com/jonboulle/clockwork"
log "github.com/sirupsen/logrus"
"github.com/gravitational/teleport"
apievents "github.com/gravitational/teleport/api/types/events"
"github.com/gravitational/teleport/lib/session"
"github.com/gravitational/teleport/lib/utils"
)
// AsyncBufferSize is a default buffer size for async emitters
const AsyncBufferSize = 1024
// AsyncEmitterConfig provides parameters for emitter
type AsyncEmitterConfig struct {
// Inner emits events to the underlying store
Inner apievents.Emitter
// BufferSize is a default buffer size for emitter
BufferSize int
}
// CheckAndSetDefaults checks and sets default values
func (c *AsyncEmitterConfig) CheckAndSetDefaults() error {
if c.Inner == nil {
return trace.BadParameter("missing parameter Inner")
}
if c.BufferSize == 0 {
c.BufferSize = AsyncBufferSize
}
return nil
}
// NewAsyncEmitter returns emitter that submits events
// without blocking the caller. It will start losing events
// on buffer overflow.
func NewAsyncEmitter(cfg AsyncEmitterConfig) (*AsyncEmitter, error) {
if err := cfg.CheckAndSetDefaults(); err != nil {
return nil, trace.Wrap(err)
}
ctx, cancel := context.WithCancel(context.Background())
a := &AsyncEmitter{
cancel: cancel,
ctx: ctx,
eventsCh: make(chan apievents.AuditEvent, cfg.BufferSize),
cfg: cfg,
}
go a.forward()
return a, nil
}
// AsyncEmitter accepts events to a buffered channel and emits
// events in a separate goroutine without blocking the caller.
type AsyncEmitter struct {
cfg AsyncEmitterConfig
eventsCh chan apievents.AuditEvent
cancel context.CancelFunc
ctx context.Context
}
// Close closes emitter and cancels all in flight events.
func (a *AsyncEmitter) Close() error {
a.cancel()
return nil
}
func (a *AsyncEmitter) forward() {
for {
select {
case <-a.ctx.Done():
return
case event := <-a.eventsCh:
err := a.cfg.Inner.EmitAuditEvent(a.ctx, event)
if err != nil {
log.WithError(err).Errorf("Failed to emit audit event.")
}
}
}
}
// EmitAuditEvent emits audit event without blocking the caller. It will start
// losing events on buffer overflow, but it never fails.
func (a *AsyncEmitter) EmitAuditEvent(ctx context.Context, event apievents.AuditEvent) error {
select {
case a.eventsCh <- event:
return nil
case <-ctx.Done():
return trace.ConnectionProblem(ctx.Err(), "context canceled or closed")
default:
log.Errorf("Failed to emit audit event %v(%v). This server's connection to the auth service appears to be slow.", event.GetType(), event.GetCode())
return nil
}
}
// CheckingEmitterConfig provides parameters for emitter
type CheckingEmitterConfig struct {
// Inner emits events to the underlying store
Inner apievents.Emitter
// Clock is a clock interface, used in tests
Clock clockwork.Clock
// UIDGenerator is unique ID generator
UIDGenerator utils.UID
// ClusterName specifies the name of this teleport cluster
// as configured on the auth server
ClusterName string
}
// NewCheckingEmitter returns emitter that checks
// that all required fields are properly set
func NewCheckingEmitter(cfg CheckingEmitterConfig) (*CheckingEmitter, error) {
if err := cfg.CheckAndSetDefaults(); err != nil {
return nil, trace.Wrap(err)
}
return &CheckingEmitter{
CheckingEmitterConfig: cfg,
}, nil
}
// CheckingEmitter ensures that event fields have been set properly
// and reports statistics for every wrapper
type CheckingEmitter struct {
CheckingEmitterConfig
}
// CheckAndSetDefaults checks and sets default values
func (w *CheckingEmitterConfig) CheckAndSetDefaults() error {
if w.Inner == nil {
return trace.BadParameter("missing parameter Inner")
}
if w.Clock == nil {
w.Clock = clockwork.NewRealClock()
}
if w.UIDGenerator == nil {
w.UIDGenerator = utils.NewRealUID()
}
return nil
}
// EmitAuditEvent emits audit event
func (r *CheckingEmitter) EmitAuditEvent(ctx context.Context, event apievents.AuditEvent) error {
auditEmitEvent.Inc()
if err := checkAndSetEventFields(event, r.Clock, r.UIDGenerator, r.ClusterName); err != nil {
log.WithError(err).Errorf("Failed to emit audit event.")
AuditFailedEmit.Inc()
return trace.Wrap(err)
}
if err := r.Inner.EmitAuditEvent(ctx, event); err != nil {
AuditFailedEmit.Inc()
log.WithError(err).Errorf("Failed to emit audit event.")
return trace.Wrap(err)
}
return nil
}
// checkAndSetEventFields updates passed event fields with additional information
// common for all event types such as unique IDs, timestamps, codes, etc.
//
// This method is a "final stop" for various audit log implementations for
// updating event fields before it gets persisted in the backend.
func checkAndSetEventFields(event apievents.AuditEvent, clock clockwork.Clock, uid utils.UID, clusterName string) error {
if event.GetType() == "" {
return trace.BadParameter("missing mandatory event type field")
}
if event.GetCode() == "" && event.GetType() != SessionPrintEvent && event.GetType() != DesktopRecordingEvent {
return trace.BadParameter("missing mandatory event code field for %v event", event.GetType())
}
if event.GetID() == "" && event.GetType() != SessionPrintEvent && event.GetType() != DesktopRecordingEvent {
event.SetID(uid.New())
}
if event.GetTime().IsZero() {
event.SetTime(clock.Now().UTC().Round(time.Millisecond))
}
if event.GetClusterName() == "" {
event.SetClusterName(clusterName)
}
return nil
}
// NewWriterEmitter returns a new instance of emitter writing to writer
func NewWriterEmitter(w io.WriteCloser) *WriterEmitter {
return &WriterEmitter{
w: w,
WriterLog: NewWriterLog(w),
}
}
// WriterEmitter is an emitter that emits all events
// to the external writer
type WriterEmitter struct {
w io.WriteCloser
*WriterLog
}
// Close closes the underlying io.WriteCloser passed in NewWriterEmitter
func (w *WriterEmitter) Close() error {
return trace.NewAggregate(
w.w.Close(),
w.WriterLog.Close())
}
// EmitAuditEvent writes the event to the writer
func (w *WriterEmitter) EmitAuditEvent(ctx context.Context, event apievents.AuditEvent) error {
// line is the text to be logged
line, err := utils.FastMarshal(event)
if err != nil {
return trace.Wrap(err)
}
_, err = fmt.Fprintln(w.w, string(line))
return trace.ConvertSystemError(err)
}
// NewLoggingEmitter returns an emitter that logs all events to the console
// with the info level
func NewLoggingEmitter() *LoggingEmitter {
return &LoggingEmitter{}
}
// LoggingEmitter logs all events with info level
type LoggingEmitter struct {
}
// EmitAuditEvent logs audit event, skips session print events, session
// disk events and app session request events, because they are very verbose.
func (*LoggingEmitter) EmitAuditEvent(ctx context.Context, event apievents.AuditEvent) error {
switch event.GetType() {
case ResizeEvent, SessionDiskEvent, SessionPrintEvent, AppSessionRequestEvent, "":
return nil
}
data, err := utils.FastMarshal(event)
if err != nil {
return trace.Wrap(err)
}
var fields log.Fields
err = utils.FastUnmarshal(data, &fields)
if err != nil {
return trace.Wrap(err)
}
fields[trace.Component] = teleport.Component(teleport.ComponentAuditLog)
log.WithFields(fields).Infof(event.GetType())
return nil
}
// NewMultiEmitter returns emitter that writes
// events to all emitters
func NewMultiEmitter(emitters ...apievents.Emitter) *MultiEmitter {
return &MultiEmitter{
emitters: emitters,
}
}
// MultiEmitter writes audit events to multiple emitters
type MultiEmitter struct {
emitters []apievents.Emitter
}
// EmitAuditEvent emits audit event to all emitters
func (m *MultiEmitter) EmitAuditEvent(ctx context.Context, event apievents.AuditEvent) error {
var errors []error
for i := range m.emitters {
err := m.emitters[i].EmitAuditEvent(ctx, event)
if err != nil {
errors = append(errors, err)
}
}
return trace.NewAggregate(errors...)
}
// StreamerAndEmitter combines streamer and emitter to create stream emitter
type StreamerAndEmitter struct {
Streamer
apievents.Emitter
}
// CheckingStreamerConfig provides parameters for streamer
type CheckingStreamerConfig struct {
// Inner emits events to the underlying store
Inner Streamer
// Clock is a clock interface, used in tests
Clock clockwork.Clock
// UIDGenerator is unique ID generator
UIDGenerator utils.UID
// ClusterName specifies the name of this teleport cluster.
ClusterName string
}
// NewCheckingStream wraps stream and makes sure event UIDs and timing are in place
func NewCheckingStream(stream apievents.Stream, clock clockwork.Clock, clusterName string) apievents.Stream {
return &CheckingStream{
stream: stream,
clock: clock,
uidGenerator: utils.NewRealUID(),
clusterName: clusterName,
}
}
// NewCheckingStreamer returns streamer that checks
// that all required fields are properly set
func NewCheckingStreamer(cfg CheckingStreamerConfig) (*CheckingStreamer, error) {
if err := cfg.CheckAndSetDefaults(); err != nil {
return nil, trace.Wrap(err)
}
return &CheckingStreamer{
CheckingStreamerConfig: cfg,
}, nil
}
// CheckingStreamer ensures that event fields have been set properly
// and reports statistics for every wrapper
type CheckingStreamer struct {
CheckingStreamerConfig
}
// CreateAuditStream creates audit event stream
func (s *CheckingStreamer) CreateAuditStream(ctx context.Context, sid session.ID) (apievents.Stream, error) {
stream, err := s.Inner.CreateAuditStream(ctx, sid)
if err != nil {
return nil, trace.Wrap(err)
}
return &CheckingStream{
clock: s.CheckingStreamerConfig.Clock,
uidGenerator: s.CheckingStreamerConfig.UIDGenerator,
clusterName: s.CheckingStreamerConfig.ClusterName,
stream: stream,
}, nil
}
// ResumeAuditStream resumes audit event stream
func (s *CheckingStreamer) ResumeAuditStream(ctx context.Context, sid session.ID, uploadID string) (apievents.Stream, error) {
stream, err := s.Inner.ResumeAuditStream(ctx, sid, uploadID)
if err != nil {
return nil, trace.Wrap(err)
}
return &CheckingStream{
clock: s.CheckingStreamerConfig.Clock,
uidGenerator: s.CheckingStreamerConfig.UIDGenerator,
clusterName: s.CheckingStreamerConfig.ClusterName,
stream: stream,
}, nil
}
// CheckAndSetDefaults checks and sets default values
func (w *CheckingStreamerConfig) CheckAndSetDefaults() error {
if w.Inner == nil {
return trace.BadParameter("checking streamer: missing parameter Inner")
}
if w.ClusterName == "" {
return trace.BadParameter("checking streamer: missing parameter ClusterName")
}
if w.Clock == nil {
w.Clock = clockwork.NewRealClock()
}
if w.UIDGenerator == nil {
w.UIDGenerator = utils.NewRealUID()
}
return nil
}
// CheckingStream verifies every event
type CheckingStream struct {
stream apievents.Stream
clock clockwork.Clock
uidGenerator utils.UID
clusterName string
}
// Close flushes non-uploaded flight stream data without marking
// the stream completed and closes the stream instance
func (s *CheckingStream) Close(ctx context.Context) error {
return s.stream.Close(ctx)
}
// Done returns channel closed when streamer is closed
// should be used to detect sending errors
func (s *CheckingStream) Done() <-chan struct{} {
return s.stream.Done()
}
// Status returns channel receiving updates about stream status
// last event index that was uploaded and upload ID
func (s *CheckingStream) Status() <-chan apievents.StreamStatus {
return s.stream.Status()
}
// Complete closes the stream and marks it finalized
func (s *CheckingStream) Complete(ctx context.Context) error {
return s.stream.Complete(ctx)
}
// EmitAuditEvent emits audit event
func (s *CheckingStream) EmitAuditEvent(ctx context.Context, event apievents.AuditEvent) error {
if err := checkAndSetEventFields(event, s.clock, s.uidGenerator, s.clusterName); err != nil {
log.WithError(err).Errorf("Failed to emit audit event %v(%v).", event.GetType(), event.GetCode())
AuditFailedEmit.Inc()
return trace.Wrap(err)
}
if err := s.stream.EmitAuditEvent(ctx, event); err != nil {
AuditFailedEmit.Inc()
log.WithError(err).Errorf("Failed to emit audit event %v(%v).", event.GetType(), event.GetCode())
return trace.Wrap(err)
}
return nil
}
// NewTeeStreamer returns a streamer that forwards non print event
// to emitter in addition to sending them to the stream
func NewTeeStreamer(streamer Streamer, emitter apievents.Emitter) *TeeStreamer {
return &TeeStreamer{
Emitter: emitter,
streamer: streamer,
}
}
// CreateAuditStream creates audit event stream
func (t *TeeStreamer) CreateAuditStream(ctx context.Context, sid session.ID) (apievents.Stream, error) {
stream, err := t.streamer.CreateAuditStream(ctx, sid)
if err != nil {
return nil, trace.Wrap(err)
}
return &TeeStream{stream: stream, emitter: t.Emitter}, nil
}
// ResumeAuditStream resumes audit event stream
func (t *TeeStreamer) ResumeAuditStream(ctx context.Context, sid session.ID, uploadID string) (apievents.Stream, error) {
stream, err := t.streamer.ResumeAuditStream(ctx, sid, uploadID)
if err != nil {
return nil, trace.Wrap(err)
}
return &TeeStream{stream: stream, emitter: t.Emitter}, nil
}
// TeeStreamer creates streams that forwards non print events
// to emitter
type TeeStreamer struct {
apievents.Emitter
streamer Streamer
}
// TeeStream sends non print events to emitter
// in addition to the stream itself
type TeeStream struct {
emitter apievents.Emitter
stream apievents.Stream
}
// Done returns channel closed when streamer is closed
// should be used to detect sending errors
func (t *TeeStream) Done() <-chan struct{} {
return t.stream.Done()
}
// Status returns channel receiving updates about stream status
// last event index that was uploaded and upload ID
func (t *TeeStream) Status() <-chan apievents.StreamStatus {
return t.stream.Status()
}
// Close flushes non-uploaded flight stream data without marking
// the stream completed and closes the stream instance
func (t *TeeStream) Close(ctx context.Context) error {
return t.stream.Close(ctx)
}
// Complete closes the stream and marks it finalized
func (t *TeeStream) Complete(ctx context.Context) error {
return t.stream.Complete(ctx)
}
// EmitAuditEvent emits audit events and forwards session control events
// to the audit log
func (t *TeeStream) EmitAuditEvent(ctx context.Context, event apievents.AuditEvent) error {
var errors []error
if err := t.stream.EmitAuditEvent(ctx, event); err != nil {
errors = append(errors, err)
}
// Forward session events except the ones that pollute global logs
switch event.GetType() {
case ResizeEvent, SessionDiskEvent, SessionPrintEvent, AppSessionRequestEvent, DesktopRecordingEvent, "":
return trace.NewAggregate(errors...)
}
if err := t.emitter.EmitAuditEvent(ctx, event); err != nil {
errors = append(errors, err)
}
return trace.NewAggregate(errors...)
}
// NewCallbackStreamer returns streamer that invokes callback on every
// action, is used in tests to inject failures
func NewCallbackStreamer(cfg CallbackStreamerConfig) (*CallbackStreamer, error) {
if err := cfg.CheckAndSetDefaults(); err != nil {
return nil, trace.Wrap(err)
}
return &CallbackStreamer{
CallbackStreamerConfig: cfg,
}, nil
}
// CallbackStreamerConfig provides parameters for streamer
type CallbackStreamerConfig struct {
// Inner emits events to the underlying store
Inner Streamer
// OnCreateAuditStream is called on create audit stream
OnCreateAuditStream func(ctx context.Context, sid session.ID, inner Streamer) (apievents.Stream, error)
// OnResumeAuditStream is called on resuming audit stream
OnResumeAuditStream func(ctx context.Context, sid session.ID, uploadID string, inner Streamer) (apievents.Stream, error)
// OnEmitAuditEvent is called on emit audit event on a stream
OnEmitAuditEvent func(ctx context.Context, sid session.ID, event apievents.AuditEvent) error
}
// CheckAndSetDefaults checks and sets default values
func (c *CallbackStreamerConfig) CheckAndSetDefaults() error {
if c.Inner == nil {
return trace.BadParameter("missing parameter Inner")
}
return nil
}
// CallbackStreamer ensures that event fields have been set properly
// and reports statistics for every wrapper
type CallbackStreamer struct {
CallbackStreamerConfig
}
// CreateAuditStream creates audit event stream
func (s *CallbackStreamer) CreateAuditStream(ctx context.Context, sid session.ID) (apievents.Stream, error) {
var stream apievents.Stream
var err error
if s.OnCreateAuditStream != nil {
stream, err = s.OnCreateAuditStream(ctx, sid, s.Inner)
} else {
stream, err = s.Inner.CreateAuditStream(ctx, sid)
}
if err != nil {
return nil, trace.Wrap(err)
}
return &CallbackStream{
stream: stream,
streamer: s,
}, nil
}
// ResumeAuditStream resumes audit event stream
func (s *CallbackStreamer) ResumeAuditStream(ctx context.Context, sid session.ID, uploadID string) (apievents.Stream, error) {
var stream apievents.Stream
var err error
if s.OnResumeAuditStream != nil {
stream, err = s.OnResumeAuditStream(ctx, sid, uploadID, s.Inner)
} else {
stream, err = s.Inner.ResumeAuditStream(ctx, sid, uploadID)
}
if err != nil {
return nil, trace.Wrap(err)
}
return &CallbackStream{
stream: stream,
sessionID: sid,
streamer: s,
}, nil
}
// CallbackStream call
type CallbackStream struct {
stream apievents.Stream
sessionID session.ID
streamer *CallbackStreamer
}
// Close flushes non-uploaded flight stream data without marking
// the stream completed and closes the stream instance
func (s *CallbackStream) Close(ctx context.Context) error {
return s.stream.Close(ctx)
}
// Done returns channel closed when streamer is closed
// should be used to detect sending errors
func (s *CallbackStream) Done() <-chan struct{} {
return s.stream.Done()
}
// Status returns channel receiving updates about stream status
// last event index that was uploaded and upload ID
func (s *CallbackStream) Status() <-chan apievents.StreamStatus {
return s.stream.Status()
}
// Complete closes the stream and marks it finalized
func (s *CallbackStream) Complete(ctx context.Context) error {
return s.stream.Complete(ctx)
}
// EmitAuditEvent emits audit event
func (s *CallbackStream) EmitAuditEvent(ctx context.Context, event apievents.AuditEvent) error {
if s.streamer.OnEmitAuditEvent != nil {
if err := s.streamer.OnEmitAuditEvent(ctx, s.sessionID, event); err != nil {
return trace.Wrap(err)
}
}
return s.stream.EmitAuditEvent(ctx, event)
}
// NewReportingStreamer reports upload events
// to the eventsC channel, if the channel is not nil.
func NewReportingStreamer(streamer Streamer, eventsC chan UploadEvent) *ReportingStreamer {
return &ReportingStreamer{
streamer: streamer,
eventsC: eventsC,
}
}
// ReportingStreamer reports upload events
// to the eventsC channel, if the channel is not nil.
type ReportingStreamer struct {
streamer Streamer
eventsC chan UploadEvent
}
// CreateAuditStream creates audit event stream
func (s *ReportingStreamer) CreateAuditStream(ctx context.Context, sid session.ID) (apievents.Stream, error) {
stream, err := s.streamer.CreateAuditStream(ctx, sid)
if err != nil {
return nil, trace.Wrap(err)
}
return &ReportingStream{
Stream: stream,
eventsC: s.eventsC,
sessionID: sid,
}, nil
}
// ResumeAuditStream resumes audit event stream
func (s *ReportingStreamer) ResumeAuditStream(ctx context.Context, sid session.ID, uploadID string) (apievents.Stream, error) {
stream, err := s.streamer.ResumeAuditStream(ctx, sid, uploadID)
if err != nil {
return nil, trace.Wrap(err)
}
return &ReportingStream{
Stream: stream,
sessionID: sid,
eventsC: s.eventsC,
}, nil
}
// ReportingStream reports status of uploads to the events channel
type ReportingStream struct {
apievents.Stream
sessionID session.ID
eventsC chan UploadEvent
}
// Complete closes the stream and marks it finalized
func (s *ReportingStream) Complete(ctx context.Context) error {
err := s.Stream.Complete(ctx)
if s.eventsC == nil {
return trace.Wrap(err)
}
select {
case s.eventsC <- UploadEvent{
SessionID: string(s.sessionID),
Error: err,
}:
default:
log.Warningf("Skip send event on a blocked channel.")
}
return trace.Wrap(err)
}