diff --git a/api/authenticators/saml.go b/api/authenticators/saml.go index dbafa29f37a..e968236d1fd 100644 --- a/api/authenticators/saml.go +++ b/api/authenticators/saml.go @@ -45,7 +45,7 @@ func (self *SamlAuthenticator) AuthRedirectTemplate() string { } func (self *SamlAuthenticator) AddHandlers(mux *api_utils.ServeMux) error { - logger := logging.Manager.GetLogger(self.config_obj, &logging.GUIComponent) + logger := logging.GetLogger(self.config_obj, &logging.GUIComponent) key, err := crypto_utils.ParseRsaPrivateKeyFromPemStr([]byte( self.authenticator.SamlPrivateKey)) if err != nil { @@ -148,7 +148,7 @@ func (self *SamlAuthenticator) AuthenticateUserHandler( Set("roles", self.user_roles). Set("remote", r.RemoteAddr)) if err != nil { - logger := logging.Manager.GetLogger(self.config_obj, &logging.GUIComponent) + logger := logging.GetLogger(self.config_obj, &logging.GUIComponent) logger.Error("Authorization failed %v %v %v", username, err, r.RemoteAddr) } @@ -167,7 +167,7 @@ func (self *SamlAuthenticator) AuthenticateUserHandler( Set("roles", self.user_roles). Set("remote", r.RemoteAddr)) if err != nil { - logger := logging.Manager.GetLogger(self.config_obj, &logging.GUIComponent) + logger := logging.GetLogger(self.config_obj, &logging.GUIComponent) logger.Error("Role Assignment Failed %v %v", username, r.RemoteAddr) } @@ -189,7 +189,7 @@ func (self *SamlAuthenticator) AuthenticateUserHandler( Set("remote", r.RemoteAddr). Set("status", http.StatusUnauthorized)) if err != nil { - logger := logging.Manager.GetLogger(self.config_obj, &logging.GUIComponent) + logger := logging.GetLogger(self.config_obj, &logging.GUIComponent) logger.Error("no saml_user_roles set %v %v", username, r.RemoteAddr) } diff --git a/api/builder.go b/api/builder.go index c95efc6fcf2..313bcb7fad7 100644 --- a/api/builder.go +++ b/api/builder.go @@ -478,7 +478,7 @@ func StartFrontendWithAutocert( return errors.New("Frontend server not configured") } - logger := logging.Manager.GetLogger(config_obj, &logging.GUIComponent) + logger := logging.GetLogger(config_obj, &logging.GUIComponent) // Autocert directory must be unique since it is usually kept in // shared storage. @@ -550,7 +550,7 @@ func StartFrontendWithAutocert( go func() { err := http.ListenAndServe(":http", certManager.HTTPHandler(nil)) if err != nil { - logger := logging.Manager.GetLogger(config_obj, &logging.GUIComponent) + logger := logging.GetLogger(config_obj, &logging.GUIComponent) logger.Error("Failed to bind to http server: %v", err) } }() @@ -616,7 +616,7 @@ func StartHTTPGUI( return errors.New("GUI server not configured") } - logger := logging.Manager.GetLogger(config_obj, &logging.GUIComponent) + logger := logging.GetLogger(config_obj, &logging.GUIComponent) listenAddr := fmt.Sprintf("%s:%d", config_obj.GUI.BindAddress, @@ -672,7 +672,7 @@ func StartSelfSignedGUI( ctx context.Context, wg *sync.WaitGroup, config_obj *config_proto.Config, mux http.Handler) error { - logger := logging.Manager.GetLogger(config_obj, &logging.GUIComponent) + logger := logging.GetLogger(config_obj, &logging.GUIComponent) if config_obj.GUI == nil { return errors.New("GUI server not configured") } diff --git a/bin/admin_generic.go b/bin/admin_generic.go index 17781d1f3ee..390c916e2a6 100644 --- a/bin/admin_generic.go +++ b/bin/admin_generic.go @@ -21,3 +21,7 @@ func checkMutex() error { return nil } func logArgv(argv []string) error { return nil } + +func InstallAuditlogger() error { + return nil +} diff --git a/bin/admin_windows.go b/bin/admin_windows.go index 39f98243e22..ddd3c005e01 100644 --- a/bin/admin_windows.go +++ b/bin/admin_windows.go @@ -7,8 +7,10 @@ import ( "fmt" "syscall" + log "github.com/sirupsen/logrus" "golang.org/x/sys/windows/svc/eventlog" "www.velocidex.com/golang/velociraptor/json" + logging "www.velocidex.com/golang/velociraptor/logging" vql_subsystem "www.velocidex.com/golang/velociraptor/vql" ) @@ -47,3 +49,42 @@ func logArgv(argv []string) error { return logger.Info(1000, msg) } + +type EventlogHook struct { + logger *eventlog.Log +} + +func (self *EventlogHook) Fire(entry *log.Entry) error { + msg, err := entry.String() + if err != nil { + fmt.Printf("Eror: %v\n", err) + return nil + } + return self.logger.Info(1000, msg) +} + +func (self *EventlogHook) Levels() []log.Level { + return []log.Level{log.InfoLevel, log.WarnLevel, log.ErrorLevel} +} + +func InstallAuditlogger() error { + source_name := "Velociraptor" + err := eventlog.InstallAsEventCreate( + source_name, eventlog.Error|eventlog.Warning|eventlog.Info) + if err != nil { + // If we fail to register our own source (maybe due to + // permission errors) we steal the .NET source + source_name = ".NET Runtime" + } + + logger, err := eventlog.Open(source_name) + if err != nil { + return err + } + hook := &EventlogHook{ + logger: logger, + } + + logging.Manager().AddHook(hook, &logging.Audit) + return nil +} diff --git a/bin/client.go b/bin/client.go index 68188e287ad..98c56386330 100644 --- a/bin/client.go +++ b/bin/client.go @@ -63,6 +63,11 @@ func doClient() error { return fmt.Errorf("Unable to load config file: %w", err) } + err = InstallAuditlogger() + if err != nil { + return err + } + return RunClient(ctx, config_obj) } diff --git a/bin/collector_test.go b/bin/collector_test.go index 2a8c01d30a3..d8a39e53cde 100644 --- a/bin/collector_test.go +++ b/bin/collector_test.go @@ -254,7 +254,6 @@ func (self *CollectorTestSuite) TestCollectorPlain() { "--args", "target=ZIP", "--args", "opt_admin=N", "--args", "opt_prompt=N", - "--args", "template=Custom.TestArtifact", "--output", output_zip, } diff --git a/bin/installer_windows.go b/bin/installer_windows.go index ced84d6c6ef..c100e375369 100644 --- a/bin/installer_windows.go +++ b/bin/installer_windows.go @@ -582,6 +582,7 @@ func runOnce(ctx context.Context, } maybeWritePanicFile(log_name, config_obj) + _ = InstallAuditlogger() writeback_service := writeback.GetWritebackService() writeback, err := writeback_service.GetWriteback(config_obj) diff --git a/http_comms/service.go b/http_comms/service.go index 9ca9fb5626a..bf77e73b27d 100644 --- a/http_comms/service.go +++ b/http_comms/service.go @@ -5,9 +5,11 @@ import ( "fmt" "sync" + "github.com/Velocidex/ordereddict" config_proto "www.velocidex.com/golang/velociraptor/config/proto" crypto_client "www.velocidex.com/golang/velociraptor/crypto/client" "www.velocidex.com/golang/velociraptor/executor" + "www.velocidex.com/golang/velociraptor/services" "www.velocidex.com/golang/velociraptor/services/writeback" "www.velocidex.com/golang/velociraptor/utils" ) @@ -36,6 +38,15 @@ func StartHttpCommunicatorService( return nil, err } + err = services.LogAudit(ctx, config_obj, + utils.GetSuperuserName(config_obj), "client_communicator", + ordereddict.NewDict(). + Set("server_urls", config_obj.Client.ServerUrls)) + if err != nil { + fmt.Printf("Error: %v\n", err) + return nil, err + } + // Now start the communicator so we can talk with the server. comm, err := NewHTTPCommunicator( ctx, diff --git a/logging/logging.go b/logging/logging.go index 49ad4dd2e7d..a808570df8a 100644 --- a/logging/logging.go +++ b/logging/logging.go @@ -55,8 +55,9 @@ var ( Audit = "VelociraptorAudit" // Lock for log manager. - mu sync.Mutex - Manager *LogManager + mu sync.Mutex + manager *LogManager + disable_log_to_files bool node_name = "" @@ -69,6 +70,13 @@ var ( closing_tag_regex = regexp.MustCompile("") ) +func Manager() *LogManager { + mu.Lock() + defer mu.Unlock() + + return manager +} + func SetNodeName(name string) { mu.Lock() defer mu.Unlock() @@ -86,8 +94,7 @@ func DisableLogging() { } func InitLogging(config_obj *config_proto.Config) error { - mu.Lock() - Manager = &LogManager{ + new_manager := &LogManager{ contexts: make(map[*string]*LogContext), } @@ -102,21 +109,23 @@ func InitLogging(config_obj *config_proto.Config) error { } for _, component := range components { - logger, err := Manager.makeNewComponent(config_obj, component) + logger, err := new_manager.makeNewComponent(config_obj, component) if err != nil { mu.Unlock() return err } - Manager.contexts[component] = logger + new_manager.contexts[component] = logger } - err := maybeAddRemoteSyslog(config_obj, Manager) - mu.Unlock() - + err := maybeAddRemoteSyslog(config_obj, new_manager) if err != nil { return err } + mu.Lock() + manager = new_manager + mu.Unlock() + FlushPrelogs(config_obj) return nil @@ -274,6 +283,16 @@ type LogManager struct { contexts map[*string]*LogContext } +func (self *LogManager) AddHook(hook logrus.Hook, component *string) { + self.mu.Lock() + defer self.mu.Unlock() + + v, pres := self.contexts[component] + if pres { + v.Logger.Hooks.Add(hook) + } +} + // Get the logger from cache - creating it if it needs to. func (self *LogManager) GetLogger( config_obj *config_proto.Config, @@ -314,8 +333,11 @@ func (self *LogManager) Reset() { } func Reset() { - if Manager != nil { - Manager.Reset() + mu.Lock() + defer mu.Unlock() + + if manager != nil { + manager.Reset() } } @@ -469,7 +491,7 @@ func AddLogFile(filename string) error { logrus.WarnLevel: fd, } - for _, log := range Manager.contexts { + for _, log := range Manager().contexts { log.Hooks.Add(lfshook.NewHook( writer_map, &JSONFormatter{&logrus.JSONFormatter{ DisableHTMLEscape: true, @@ -515,17 +537,16 @@ func NewPlainLogger( } func GetLogger(config_obj *config_proto.Config, component *string) *LogContext { - mu.Lock() - lManager := Manager - mu.Unlock() - + lManager := Manager() if lManager == nil { err := InitLogging(config_obj) if err != nil { panic(err) } + lManager = Manager() + } - return Manager.GetLogger(config_obj, component) + return lManager.GetLogger(config_obj, component) } type stackTracer interface { diff --git a/logging/syslog_nonwindows.go b/logging/syslog_nonwindows.go index f64ed49af40..d04c00032fd 100644 --- a/logging/syslog_nonwindows.go +++ b/logging/syslog_nonwindows.go @@ -1,3 +1,4 @@ +//go:build !windows // +build !windows package logging @@ -64,11 +65,8 @@ func maybeAddRemoteSyslog( return err } - for k, v := range manager.contexts { - _, pres := components[k] - if pres { - v.Logger.Hooks.Add(hook) - } + for k := range components { + manager.AddHook(hook, k) } return nil diff --git a/services/audit_manager/audit_manager.go b/services/audit_manager/audit_manager.go index 56615d97499..e55cb473133 100644 --- a/services/audit_manager/audit_manager.go +++ b/services/audit_manager/audit_manager.go @@ -8,6 +8,7 @@ import ( config_proto "www.velocidex.com/golang/velociraptor/config/proto" "www.velocidex.com/golang/velociraptor/logging" "www.velocidex.com/golang/velociraptor/services" + "www.velocidex.com/golang/velociraptor/utils" ) type AuditManager struct{} @@ -26,6 +27,11 @@ func (self *AuditManager) LogAudit( logger := logging.GetLogger(config_obj, &logging.Audit) logger.WithFields(logrus.Fields(record.ToMap())).Info(operation) + // Only forward the event if running on the server. + if utils.RunningOnClient(config_obj) { + return nil + } + journal, err := services.GetJournal(config_obj) if err != nil { return err diff --git a/utils/context.go b/utils/context.go new file mode 100644 index 00000000000..6ab8841783f --- /dev/null +++ b/utils/context.go @@ -0,0 +1,25 @@ +package utils + +import config_proto "www.velocidex.com/golang/velociraptor/config/proto" + +// True when we are running on the client. +func RunningOnClient(config_obj *config_proto.Config) bool { + // Pure clients do not have any frontend configs. + if config_obj.Frontend == nil || config_obj.Datastore == nil { + return true + } + + // Clients also run the event table service. + if config_obj.Services != nil { + if config_obj.Services.ClientEventTable { + return true + } + + // Server only run the hunt dispatcher. + if config_obj.Services.HuntDispatcher { + return false + } + } + + return false +}