From 16287fa5f71f4ad083abc74e1fc020b697946c8a Mon Sep 17 00:00:00 2001 From: Mike Cohen Date: Tue, 20 Feb 2024 01:29:57 +1000 Subject: [PATCH] Added a watch_json() plugin (#3298) This is based on the watch_syslog() plugin. Also added tests and bugfixes to watch_syslog() Also suppress detailed errors to non-API HTTP requests like the public directory handler. --- api/authenticators/logging.go | 33 +- api/handlers.go | 32 +- bin/debug.go | 2 + config/proto/config.pb.go | 389 ++++++++++-------- config/proto/config.proto | 12 +- docs/references/sample_config/main.go | 1 + docs/references/server.config.yaml | 20 +- docs/references/vql.yaml | 40 +- server/comms.go | 50 ++- server/metrics.go | 5 +- utils/http/logging.go | 24 ++ vql/parsers/json.go | 107 ++++- vql/parsers/syslog/auditd.go | 2 +- .../syslog/fixtures/TestSyslogReader.golden | 59 +++ vql/parsers/syslog/scanner.go | 8 +- vql/parsers/syslog/watcher.go | 159 ++++--- vql/parsers/syslog/watcher_test.go | 302 ++++++++++++++ 17 files changed, 920 insertions(+), 325 deletions(-) create mode 100644 utils/http/logging.go create mode 100644 vql/parsers/syslog/fixtures/TestSyslogReader.golden create mode 100644 vql/parsers/syslog/watcher_test.go diff --git a/api/authenticators/logging.go b/api/authenticators/logging.go index c6ccac206c8..f60c57d5103 100644 --- a/api/authenticators/logging.go +++ b/api/authenticators/logging.go @@ -10,29 +10,9 @@ import ( "www.velocidex.com/golang/velociraptor/constants" "www.velocidex.com/golang/velociraptor/json" "www.velocidex.com/golang/velociraptor/logging" + http_utils "www.velocidex.com/golang/velociraptor/utils/http" ) -// Record the status of the request so we can log it. -type statusRecorder struct { - http.ResponseWriter - http.Flusher - status int - error []byte -} - -func (self *statusRecorder) WriteHeader(code int) { - self.status = code - self.ResponseWriter.WriteHeader(code) -} - -func (self *statusRecorder) Write(buf []byte) (int, error) { - if self.status == 500 { - self.error = buf - } - - return self.ResponseWriter.Write(buf) -} - func GetUserInfo(ctx context.Context, config_obj *config_proto.Config) *api_proto.VelociraptorUser { result := &api_proto.VelociraptorUser{} @@ -51,22 +31,23 @@ func GetUserInfo(ctx context.Context, func GetLoggingHandler(config_obj *config_proto.Config) func(http.Handler) http.Handler { logger := logging.GetLogger(config_obj, &logging.GUIComponent) + return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - rec := &statusRecorder{ + rec := &http_utils.StatusRecorder{ w, w.(http.Flusher), 200, nil} defer func() { - if rec.status == 500 { + if rec.Status == 500 { logger.WithFields( logrus.Fields{ "method": r.Method, "url": r.URL.Path, "remote": r.RemoteAddr, - "error": string(rec.error), + "error": string(rec.Error), "user-agent": r.UserAgent(), - "status": rec.status, + "status": rec.Status, "user": GetUserInfo( r.Context(), config_obj).Name, }).Error("") @@ -78,7 +59,7 @@ func GetLoggingHandler(config_obj *config_proto.Config) func(http.Handler) http. "url": r.URL.Path, "remote": r.RemoteAddr, "user-agent": r.UserAgent(), - "status": rec.status, + "status": rec.Status, "user": GetUserInfo( r.Context(), config_obj).Name, }).Info("") diff --git a/api/handlers.go b/api/handlers.go index 1c81424c4a1..08613159f36 100644 --- a/api/handlers.go +++ b/api/handlers.go @@ -27,29 +27,9 @@ import ( "www.velocidex.com/golang/velociraptor/constants" "www.velocidex.com/golang/velociraptor/json" "www.velocidex.com/golang/velociraptor/logging" + http_utils "www.velocidex.com/golang/velociraptor/utils/http" ) -// Record the status of the request so we can log it. -type statusRecorder struct { - http.ResponseWriter - http.Flusher - status int - error []byte -} - -func (self *statusRecorder) WriteHeader(code int) { - self.status = code - self.ResponseWriter.WriteHeader(code) -} - -func (self *statusRecorder) Write(buf []byte) (int, error) { - if self.status == 500 { - self.error = buf - } - - return self.ResponseWriter.Write(buf) -} - func GetUserInfo(ctx context.Context, config_obj *config_proto.Config) *api_proto.VelociraptorUser { result := &api_proto.VelociraptorUser{} @@ -70,20 +50,20 @@ func GetLoggingHandler(config_obj *config_proto.Config) func(http.Handler) http. logger := logging.GetLogger(config_obj, &logging.GUIComponent) return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - rec := &statusRecorder{ + rec := &http_utils.StatusRecorder{ w, w.(http.Flusher), 200, nil} defer func() { - if rec.status == 500 { + if rec.Status == 500 { logger.WithFields( logrus.Fields{ "method": r.Method, "url": r.URL.Path, "remote": r.RemoteAddr, - "error": string(rec.error), + "error": string(rec.Error), "user-agent": r.UserAgent(), - "status": rec.status, + "status": rec.Status, "user": GetUserInfo( r.Context(), config_obj).Name, }).Error("") @@ -95,7 +75,7 @@ func GetLoggingHandler(config_obj *config_proto.Config) func(http.Handler) http. "url": r.URL.Path, "remote": r.RemoteAddr, "user-agent": r.UserAgent(), - "status": rec.status, + "status": rec.Status, "user": GetUserInfo( r.Context(), config_obj).Name, }).Info("") diff --git a/bin/debug.go b/bin/debug.go index 48b280c5796..6e20f38ab6a 100644 --- a/bin/debug.go +++ b/bin/debug.go @@ -309,6 +309,8 @@ func handleIndex(w http.ResponseWriter, r *http.Request) { func initDebugServer(config_obj *config_proto.Config) error { if *debug_flag { + config_obj.DebugMode = true + logger := logging.GetLogger(config_obj, &logging.FrontendComponent) logger.Info("Starting debug server on http://127.0.0.1:%v/debug/pprof", *debug_flag_port) diff --git a/config/proto/config.pb.go b/config/proto/config.pb.go index 99239f89b9a..2efb8e92498 100644 --- a/config/proto/config.pb.go +++ b/config/proto/config.pb.go @@ -3499,9 +3499,15 @@ type Defaults struct { // Normally the inventory service attempts to download tools in // its own but if this is set, we prevent any external access. DisableInventoryServiceExternalAccess bool `protobuf:"varint,34,opt,name=disable_inventory_service_external_access,json=disableInventoryServiceExternalAccess,proto3" json:"disable_inventory_service_external_access,omitempty"` - // Controls how exports work (creating hunt or colletion exports to a zip file). + // Controls how exports work (creating hunt or colletion exports + // to a zip file). ExportConcurrency int64 `protobuf:"varint,40,opt,name=export_concurrency,json=exportConcurrency,proto3" json:"export_concurrency,omitempty"` ExportMaxTimeoutSec int64 `protobuf:"varint,41,opt,name=export_max_timeout_sec,json=exportMaxTimeoutSec,proto3" json:"export_max_timeout_sec,omitempty"` + // Watch plugin frequency sleep time in seconds: How often + // watch_syslog() will check for changes (default 3). + WatchPluginFrequency int64 `protobuf:"varint,44,opt,name=watch_plugin_frequency,json=watchPluginFrequency,proto3" json:"watch_plugin_frequency,omitempty"` + // Maximum length of the line that will be parsed (16kb) + WatchPluginBufferSize int64 `protobuf:"varint,45,opt,name=watch_plugin_buffer_size,json=watchPluginBufferSize,proto3" json:"watch_plugin_buffer_size,omitempty"` } func (x *Defaults) Reset() { @@ -3767,6 +3773,20 @@ func (x *Defaults) GetExportMaxTimeoutSec() int64 { return 0 } +func (x *Defaults) GetWatchPluginFrequency() int64 { + if x != nil { + return x.WatchPluginFrequency + } + return 0 +} + +func (x *Defaults) GetWatchPluginBufferSize() int64 { + if x != nil { + return x.WatchPluginBufferSize + } + return 0 +} + // Configures crypto preferences type CryptoConfig struct { state protoimpl.MessageState @@ -4141,8 +4161,9 @@ type Config struct { // internally that mark each org's config object. They should // definitely not be set on the client's config because the client // does not know or use its own org id. - OrgId string `protobuf:"bytes,36,opt,name=org_id,json=orgId,proto3" json:"org_id,omitempty"` - OrgName string `protobuf:"bytes,37,opt,name=org_name,json=orgName,proto3" json:"org_name,omitempty"` + OrgId string `protobuf:"bytes,36,opt,name=org_id,json=orgId,proto3" json:"org_id,omitempty"` + OrgName string `protobuf:"bytes,37,opt,name=org_name,json=orgName,proto3" json:"org_name,omitempty"` + DebugMode bool `protobuf:"varint,41,opt,name=debug_mode,json=debugMode,proto3" json:"debug_mode,omitempty"` // The services that will run at initialization. Note this is not // set in the config file by the user but is propagated from the // startup code. @@ -4365,6 +4386,13 @@ func (x *Config) GetOrgName() string { return "" } +func (x *Config) GetDebugMode() bool { + if x != nil { + return x.DebugMode + } + return false +} + func (x *Config) GetServices() *ServerServicesConfig { if x != nil { return x.Services @@ -5517,7 +5545,7 @@ var file_config_proto_rawDesc = []byte{ 0x6f, 0x6d, 0x6d, 0x75, 0x6e, 0x69, 0x63, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x2c, 0x0a, 0x12, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x1c, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x45, - 0x76, 0x65, 0x6e, 0x74, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x22, 0x83, 0x0e, 0x0a, 0x08, 0x44, 0x65, + 0x76, 0x65, 0x6e, 0x74, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x22, 0xf2, 0x0e, 0x0a, 0x08, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x68, 0x75, 0x6e, 0x74, 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x5f, 0x68, 0x6f, 0x75, 0x72, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x68, 0x75, 0x6e, 0x74, 0x45, 0x78, 0x70, 0x69, 0x72, 0x79, 0x48, 0x6f, 0x75, @@ -5629,179 +5657,188 @@ var file_config_proto_rawDesc = []byte{ 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x33, 0x0a, 0x16, 0x65, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x5f, 0x73, 0x65, 0x63, 0x18, 0x29, 0x20, 0x01, 0x28, 0x03, 0x52, 0x13, 0x65, 0x78, 0x70, 0x6f, - 0x72, 0x74, 0x4d, 0x61, 0x78, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x53, 0x65, 0x63, 0x22, - 0xad, 0x04, 0x0a, 0x0c, 0x43, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x73, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x6f, 0x6f, 0x74, 0x43, 0x65, 0x72, 0x74, 0x73, 0x12, - 0x7f, 0x0a, 0x17, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x5f, 0x74, - 0x68, 0x75, 0x6d, 0x62, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, - 0x42, 0x46, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, 0x40, 0x12, 0x3e, 0x53, 0x48, 0x41, 0x32, 0x35, 0x36, - 0x20, 0x74, 0x68, 0x75, 0x6d, 0x62, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x73, 0x20, 0x6f, 0x66, 0x20, - 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x20, 0x74, 0x68, 0x61, - 0x74, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x20, 0x77, 0x69, 0x6c, - 0x6c, 0x20, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x52, 0x16, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, - 0x69, 0x63, 0x61, 0x74, 0x65, 0x54, 0x68, 0x75, 0x6d, 0x62, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x73, - 0x12, 0xd5, 0x01, 0x0a, 0x1d, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, - 0x5f, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x6f, - 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x90, 0x01, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, - 0x89, 0x01, 0x12, 0x86, 0x01, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x73, 0x20, 0x74, 0x68, 0x65, - 0x20, 0x77, 0x61, 0x79, 0x20, 0x69, 0x6e, 0x20, 0x77, 0x68, 0x69, 0x63, 0x68, 0x20, 0x56, 0x65, - 0x6c, 0x6f, 0x63, 0x69, 0x72, 0x61, 0x70, 0x74, 0x6f, 0x72, 0x20, 0x76, 0x65, 0x72, 0x69, 0x66, - 0x69, 0x65, 0x73, 0x20, 0x54, 0x4c, 0x53, 0x20, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, - 0x61, 0x74, 0x65, 0x73, 0x2e, 0x20, 0x41, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x20, 0x76, 0x61, - 0x6c, 0x75, 0x65, 0x73, 0x3a, 0x20, 0x50, 0x4b, 0x49, 0x20, 0x28, 0x74, 0x68, 0x65, 0x20, 0x64, - 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x29, 0x2c, 0x20, 0x50, 0x4b, 0x49, 0x5f, 0x4f, 0x52, 0x5f, - 0x54, 0x48, 0x55, 0x4d, 0x42, 0x50, 0x52, 0x49, 0x4e, 0x54, 0x2c, 0x20, 0x54, 0x48, 0x55, 0x4d, - 0x42, 0x50, 0x52, 0x49, 0x4e, 0x54, 0x5f, 0x4f, 0x4e, 0x4c, 0x59, 0x52, 0x1b, 0x63, 0x65, 0x72, - 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x31, 0x0a, 0x15, 0x61, 0x6c, 0x6c, 0x6f, - 0x77, 0x5f, 0x77, 0x65, 0x61, 0x6b, 0x5f, 0x74, 0x6c, 0x73, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, - 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x57, 0x65, - 0x61, 0x6b, 0x54, 0x6c, 0x73, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x2d, 0x0a, 0x12, 0x63, - 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, - 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, - 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x43, 0x0a, 0x1e, 0x63, 0x6c, + 0x72, 0x74, 0x4d, 0x61, 0x78, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x53, 0x65, 0x63, 0x12, + 0x34, 0x0a, 0x16, 0x77, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x5f, + 0x66, 0x72, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x2c, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x14, 0x77, 0x61, 0x74, 0x63, 0x68, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x46, 0x72, 0x65, 0x71, + 0x75, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x37, 0x0a, 0x18, 0x77, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x70, + 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x5f, 0x62, 0x75, 0x66, 0x66, 0x65, 0x72, 0x5f, 0x73, 0x69, 0x7a, + 0x65, 0x18, 0x2d, 0x20, 0x01, 0x28, 0x03, 0x52, 0x15, 0x77, 0x61, 0x74, 0x63, 0x68, 0x50, 0x6c, + 0x75, 0x67, 0x69, 0x6e, 0x42, 0x75, 0x66, 0x66, 0x65, 0x72, 0x53, 0x69, 0x7a, 0x65, 0x22, 0xad, + 0x04, 0x0a, 0x0c, 0x43, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, + 0x1d, 0x0a, 0x0a, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x73, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x6f, 0x6f, 0x74, 0x43, 0x65, 0x72, 0x74, 0x73, 0x12, 0x7f, + 0x0a, 0x17, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x68, + 0x75, 0x6d, 0x62, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x42, + 0x46, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, 0x40, 0x12, 0x3e, 0x53, 0x48, 0x41, 0x32, 0x35, 0x36, 0x20, + 0x74, 0x68, 0x75, 0x6d, 0x62, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x73, 0x20, 0x6f, 0x66, 0x20, 0x63, + 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x20, 0x74, 0x68, 0x61, 0x74, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x20, 0x77, 0x69, 0x6c, 0x6c, + 0x20, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x52, 0x16, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, + 0x63, 0x61, 0x74, 0x65, 0x54, 0x68, 0x75, 0x6d, 0x62, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x73, 0x12, + 0xd5, 0x01, 0x0a, 0x1d, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x5f, + 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x6f, 0x64, + 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x90, 0x01, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, 0x89, + 0x01, 0x12, 0x86, 0x01, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, + 0x77, 0x61, 0x79, 0x20, 0x69, 0x6e, 0x20, 0x77, 0x68, 0x69, 0x63, 0x68, 0x20, 0x56, 0x65, 0x6c, + 0x6f, 0x63, 0x69, 0x72, 0x61, 0x70, 0x74, 0x6f, 0x72, 0x20, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, + 0x65, 0x73, 0x20, 0x54, 0x4c, 0x53, 0x20, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, + 0x74, 0x65, 0x73, 0x2e, 0x20, 0x41, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x20, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x73, 0x3a, 0x20, 0x50, 0x4b, 0x49, 0x20, 0x28, 0x74, 0x68, 0x65, 0x20, 0x64, 0x65, + 0x66, 0x61, 0x75, 0x6c, 0x74, 0x29, 0x2c, 0x20, 0x50, 0x4b, 0x49, 0x5f, 0x4f, 0x52, 0x5f, 0x54, + 0x48, 0x55, 0x4d, 0x42, 0x50, 0x52, 0x49, 0x4e, 0x54, 0x2c, 0x20, 0x54, 0x48, 0x55, 0x4d, 0x42, + 0x50, 0x52, 0x49, 0x4e, 0x54, 0x5f, 0x4f, 0x4e, 0x4c, 0x59, 0x52, 0x1b, 0x63, 0x65, 0x72, 0x74, + 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x31, 0x0a, 0x15, 0x61, 0x6c, 0x6c, 0x6f, 0x77, + 0x5f, 0x77, 0x65, 0x61, 0x6b, 0x5f, 0x74, 0x6c, 0x73, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x57, 0x65, 0x61, + 0x6b, 0x54, 0x6c, 0x73, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x2d, 0x0a, 0x12, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, - 0x5f, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x06, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x1b, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, - 0x69, 0x63, 0x61, 0x74, 0x65, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x22, - 0x5d, 0x0a, 0x0a, 0x4d, 0x6f, 0x75, 0x6e, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x1a, 0x0a, - 0x08, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x08, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x6f, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x72, 0x65, - 0x66, 0x69, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x72, 0x65, 0x66, 0x69, - 0x78, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x74, 0x68, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x74, 0x68, 0x54, 0x79, 0x70, 0x65, 0x22, 0xf0, - 0x02, 0x0a, 0x0f, 0x52, 0x65, 0x6d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, - 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, - 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x63, 0x6f, 0x70, - 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x12, 0x25, - 0x0a, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4d, 0x6f, 0x75, 0x6e, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, - 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x12, 0x21, 0x0a, 0x02, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x11, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4d, 0x6f, 0x75, 0x6e, 0x74, 0x50, - 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x02, 0x6f, 0x6e, 0x12, 0x20, 0x0a, 0x0b, 0x70, 0x65, 0x72, 0x6d, - 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x70, - 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x0e, 0x0a, 0x02, 0x6f, 0x73, - 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x6f, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x68, 0x6f, - 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x68, 0x6f, - 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x03, 0x65, 0x6e, 0x76, 0x18, 0x07, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x56, 0x51, 0x4c, 0x45, - 0x6e, 0x76, 0x52, 0x03, 0x65, 0x6e, 0x76, 0x12, 0x2d, 0x0a, 0x12, 0x64, 0x69, 0x73, 0x61, 0x62, - 0x6c, 0x65, 0x64, 0x5f, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x08, 0x20, - 0x03, 0x28, 0x09, 0x52, 0x11, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x46, 0x75, 0x6e, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x29, 0x0a, 0x10, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, - 0x65, 0x64, 0x5f, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x09, - 0x52, 0x0f, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, - 0x73, 0x22, 0xf7, 0x0c, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2b, 0x0a, 0x0f, - 0x61, 0x75, 0x74, 0x6f, 0x63, 0x65, 0x72, 0x74, 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, - 0x15, 0x20, 0x01, 0x28, 0x09, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0e, 0x61, 0x75, 0x74, 0x6f, 0x63, - 0x65, 0x72, 0x74, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x46, 0x0a, 0x07, 0x76, 0x65, 0x72, - 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x2e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x42, 0x1c, 0xe2, 0xfc, 0xe3, 0xc4, - 0x01, 0x16, 0x12, 0x14, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x69, 0x6e, 0x66, 0x6f, - 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, - 0x6e, 0x12, 0x4a, 0x0a, 0x06, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, - 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x42, 0x1d, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, 0x17, 0x12, 0x15, - 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x20, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x52, 0x06, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x50, 0x0a, - 0x03, 0x41, 0x50, 0x49, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x2e, 0x41, 0x50, 0x49, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x42, 0x2c, 0xe2, 0xfc, - 0xe3, 0xc4, 0x01, 0x26, 0x12, 0x24, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x67, 0x52, 0x50, 0x43, 0x20, 0x41, 0x50, 0x49, - 0x20, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x2e, 0x52, 0x03, 0x41, 0x50, 0x49, 0x12, - 0x22, 0x0a, 0x03, 0x47, 0x55, 0x49, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x47, 0x55, 0x49, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x03, - 0x47, 0x55, 0x49, 0x12, 0x1f, 0x0a, 0x02, 0x43, 0x41, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x0f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x43, 0x41, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x52, 0x02, 0x43, 0x41, 0x12, 0x31, 0x0a, 0x08, 0x46, 0x72, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x64, - 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x46, - 0x72, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x08, 0x46, - 0x72, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x64, 0x12, 0x3d, 0x0a, 0x0e, 0x45, 0x78, 0x74, 0x72, 0x61, - 0x46, 0x72, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x64, 0x73, 0x18, 0x1f, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x46, 0x72, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x64, - 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0e, 0x45, 0x78, 0x74, 0x72, 0x61, 0x46, 0x72, 0x6f, - 0x6e, 0x74, 0x65, 0x6e, 0x64, 0x73, 0x12, 0x34, 0x0a, 0x09, 0x44, 0x61, 0x74, 0x61, 0x73, 0x74, - 0x6f, 0x72, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x52, 0x09, 0x44, 0x61, 0x74, 0x61, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x32, 0x0a, 0x09, - 0x57, 0x72, 0x69, 0x74, 0x65, 0x62, 0x61, 0x63, 0x6b, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x10, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x57, 0x72, 0x69, 0x74, 0x65, 0x62, 0x61, 0x63, - 0x6b, 0x42, 0x02, 0x18, 0x01, 0x52, 0x09, 0x57, 0x72, 0x69, 0x74, 0x65, 0x62, 0x61, 0x63, 0x6b, - 0x12, 0x25, 0x0a, 0x04, 0x4d, 0x61, 0x69, 0x6c, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4d, 0x61, 0x69, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x52, 0x04, 0x4d, 0x61, 0x69, 0x6c, 0x12, 0x2e, 0x0a, 0x07, 0x4c, 0x6f, 0x67, 0x67, 0x69, - 0x6e, 0x67, 0x18, 0x17, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x2e, 0x4c, 0x6f, 0x67, 0x67, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x07, - 0x4c, 0x6f, 0x67, 0x67, 0x69, 0x6e, 0x67, 0x12, 0x2b, 0x0a, 0x06, 0x4d, 0x69, 0x6e, 0x69, 0x6f, - 0x6e, 0x18, 0x28, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, - 0x4d, 0x69, 0x6e, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x4d, 0x69, - 0x6e, 0x69, 0x6f, 0x6e, 0x12, 0x40, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x65, 0x18, - 0x14, 0x20, 0x01, 0x28, 0x08, 0x42, 0x26, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, 0x20, 0x12, 0x1e, 0x45, - 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x20, 0x76, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x65, 0x20, 0x6c, 0x6f, - 0x67, 0x67, 0x69, 0x6e, 0x67, 0x20, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x2e, 0x52, 0x07, 0x76, - 0x65, 0x72, 0x62, 0x6f, 0x73, 0x65, 0x12, 0x5c, 0x0a, 0x13, 0x61, 0x75, 0x74, 0x6f, 0x63, 0x65, - 0x72, 0x74, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x5f, 0x63, 0x61, 0x63, 0x68, 0x65, 0x18, 0x16, 0x20, - 0x01, 0x28, 0x09, 0x42, 0x2c, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, 0x26, 0x12, 0x24, 0x50, 0x61, 0x74, - 0x68, 0x20, 0x74, 0x6f, 0x20, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x20, 0x61, 0x75, 0x74, 0x6f, 0x63, - 0x65, 0x72, 0x74, 0x20, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, - 0x2e, 0x52, 0x11, 0x61, 0x75, 0x74, 0x6f, 0x63, 0x65, 0x72, 0x74, 0x43, 0x65, 0x72, 0x74, 0x43, - 0x61, 0x63, 0x68, 0x65, 0x12, 0x6e, 0x0a, 0x0a, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, - 0x6e, 0x67, 0x18, 0x19, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x2e, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x42, 0x35, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, 0x2f, 0x12, 0x2d, 0x57, 0x68, 0x65, 0x72, 0x65, - 0x20, 0x74, 0x6f, 0x20, 0x62, 0x69, 0x6e, 0x64, 0x20, 0x70, 0x72, 0x6f, 0x6d, 0x65, 0x74, 0x68, - 0x65, 0x75, 0x73, 0x20, 0x6d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x20, 0x65, - 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x2e, 0x52, 0x0a, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, - 0x72, 0x69, 0x6e, 0x67, 0x12, 0x7f, 0x0a, 0x0a, 0x61, 0x70, 0x69, 0x5f, 0x63, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x18, 0x1a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x2e, 0x41, 0x70, 0x69, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x42, 0x48, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, 0x42, 0x12, 0x40, 0x49, 0x66, 0x20, 0x77, 0x65, 0x20, - 0x6c, 0x6f, 0x61, 0x64, 0x65, 0x64, 0x20, 0x74, 0x68, 0x65, 0x20, 0x61, 0x70, 0x69, 0x20, 0x63, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x20, 0x77, 0x65, 0x20, 0x6c, 0x6f, 0x61, 0x64, 0x20, 0x74, 0x68, - 0x69, 0x73, 0x20, 0x69, 0x6e, 0x74, 0x6f, 0x20, 0x74, 0x68, 0x65, 0x20, 0x67, 0x6c, 0x6f, 0x62, - 0x61, 0x6c, 0x20, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x52, 0x09, 0x61, 0x70, 0x69, 0x43, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x8f, 0x01, 0x0a, 0x08, 0x61, 0x75, 0x74, 0x6f, 0x65, 0x78, - 0x65, 0x63, 0x18, 0x1c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x2e, 0x41, 0x75, 0x74, 0x6f, 0x45, 0x78, 0x65, 0x63, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x42, - 0x5c, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, 0x56, 0x12, 0x54, 0x49, 0x66, 0x20, 0x74, 0x68, 0x69, 0x73, - 0x20, 0x69, 0x73, 0x20, 0x73, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x65, 0x64, 0x20, 0x77, 0x65, - 0x20, 0x6c, 0x61, 0x75, 0x6e, 0x63, 0x68, 0x20, 0x74, 0x68, 0x65, 0x20, 0x62, 0x69, 0x6e, 0x61, - 0x72, 0x79, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x74, 0x68, 0x65, 0x20, 0x67, 0x69, 0x76, 0x65, - 0x6e, 0x20, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x20, 0x6c, 0x69, 0x6e, 0x65, 0x20, 0x61, - 0x75, 0x74, 0x6f, 0x6d, 0x61, 0x74, 0x69, 0x63, 0x61, 0x6c, 0x6c, 0x79, 0x2e, 0x52, 0x08, 0x61, - 0x75, 0x74, 0x6f, 0x65, 0x78, 0x65, 0x63, 0x12, 0x50, 0x0a, 0x0b, 0x73, 0x65, 0x72, 0x76, 0x65, - 0x72, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x09, 0x42, 0x2f, 0xe2, 0xfc, - 0xe3, 0xc4, 0x01, 0x29, 0x12, 0x27, 0x54, 0x79, 0x70, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x73, 0x65, - 0x72, 0x76, 0x65, 0x72, 0x20, 0x28, 0x6c, 0x69, 0x6e, 0x75, 0x78, 0x2c, 0x20, 0x77, 0x69, 0x6e, - 0x64, 0x6f, 0x77, 0x73, 0x2c, 0x20, 0x64, 0x61, 0x72, 0x77, 0x69, 0x6e, 0x29, 0x52, 0x0a, 0x73, - 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x79, 0x70, 0x65, 0x12, 0x2b, 0x0a, 0x11, 0x6f, 0x62, 0x66, - 0x75, 0x73, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x18, 0x20, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x6f, 0x62, 0x66, 0x75, 0x73, 0x63, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x12, 0x2b, 0x0a, 0x08, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, - 0x74, 0x73, 0x18, 0x21, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x2e, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x73, 0x52, 0x08, 0x64, 0x65, 0x66, 0x61, 0x75, - 0x6c, 0x74, 0x73, 0x12, 0x27, 0x0a, 0x0f, 0x61, 0x6e, 0x61, 0x6c, 0x79, 0x73, 0x69, 0x73, 0x5f, - 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x18, 0x22, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x61, 0x6e, - 0x61, 0x6c, 0x79, 0x73, 0x69, 0x73, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x12, 0x36, 0x0a, 0x0a, - 0x72, 0x65, 0x6d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x23, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x16, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x6d, 0x61, 0x70, 0x70, 0x69, - 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0a, 0x72, 0x65, 0x6d, 0x61, 0x70, 0x70, - 0x69, 0x6e, 0x67, 0x73, 0x12, 0x15, 0x0a, 0x06, 0x6f, 0x72, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x24, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6f, 0x72, 0x67, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x6f, - 0x72, 0x67, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x25, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6f, - 0x72, 0x67, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x37, 0x0a, 0x08, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, - 0x65, 0x73, 0x18, 0x26, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x43, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x08, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x12, - 0x1a, 0x0a, 0x08, 0x6c, 0x6f, 0x63, 0x6b, 0x64, 0x6f, 0x77, 0x6e, 0x18, 0x27, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x08, 0x6c, 0x6f, 0x63, 0x6b, 0x64, 0x6f, 0x77, 0x6e, 0x42, 0x34, 0x5a, 0x32, 0x77, - 0x77, 0x77, 0x2e, 0x76, 0x65, 0x6c, 0x6f, 0x63, 0x69, 0x64, 0x65, 0x78, 0x2e, 0x63, 0x6f, 0x6d, - 0x2f, 0x67, 0x6f, 0x6c, 0x61, 0x6e, 0x67, 0x2f, 0x76, 0x65, 0x6c, 0x6f, 0x63, 0x69, 0x72, 0x61, - 0x70, 0x74, 0x6f, 0x72, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2f, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x65, + 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x43, 0x0a, 0x1e, 0x63, 0x6c, 0x69, + 0x65, 0x6e, 0x74, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x5f, + 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x1b, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, + 0x63, 0x61, 0x74, 0x65, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x22, 0x5d, + 0x0a, 0x0a, 0x4d, 0x6f, 0x75, 0x6e, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, + 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, + 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x6f, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x72, 0x65, 0x66, + 0x69, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, + 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x74, 0x68, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x74, 0x68, 0x54, 0x79, 0x70, 0x65, 0x22, 0xf0, 0x02, + 0x0a, 0x0f, 0x52, 0x65, 0x6d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, + 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, + 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x12, 0x25, 0x0a, + 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x2e, 0x4d, 0x6f, 0x75, 0x6e, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x04, + 0x66, 0x72, 0x6f, 0x6d, 0x12, 0x21, 0x0a, 0x02, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x11, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4d, 0x6f, 0x75, 0x6e, 0x74, 0x50, 0x6f, + 0x69, 0x6e, 0x74, 0x52, 0x02, 0x6f, 0x6e, 0x12, 0x20, 0x0a, 0x0b, 0x70, 0x65, 0x72, 0x6d, 0x69, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x65, + 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x0e, 0x0a, 0x02, 0x6f, 0x73, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x6f, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x68, 0x6f, 0x73, + 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x68, 0x6f, 0x73, + 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x03, 0x65, 0x6e, 0x76, 0x18, 0x07, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x56, 0x51, 0x4c, 0x45, 0x6e, + 0x76, 0x52, 0x03, 0x65, 0x6e, 0x76, 0x12, 0x2d, 0x0a, 0x12, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, + 0x65, 0x64, 0x5f, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x08, 0x20, 0x03, + 0x28, 0x09, 0x52, 0x11, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x46, 0x75, 0x6e, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x29, 0x0a, 0x10, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, + 0x64, 0x5f, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x09, 0x52, + 0x0f, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x73, + 0x22, 0x96, 0x0d, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2b, 0x0a, 0x0f, 0x61, + 0x75, 0x74, 0x6f, 0x63, 0x65, 0x72, 0x74, 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x15, + 0x20, 0x01, 0x28, 0x09, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0e, 0x61, 0x75, 0x74, 0x6f, 0x63, 0x65, + 0x72, 0x74, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x46, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, + 0x69, 0x6f, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x2e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x42, 0x1c, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, + 0x16, 0x12, 0x14, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x69, 0x6e, 0x66, 0x6f, 0x72, + 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, + 0x12, 0x4a, 0x0a, 0x06, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x42, 0x1d, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, 0x17, 0x12, 0x15, 0x43, + 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x20, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x2e, 0x52, 0x06, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x50, 0x0a, 0x03, + 0x41, 0x50, 0x49, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x2e, 0x41, 0x50, 0x49, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x42, 0x2c, 0xe2, 0xfc, 0xe3, + 0xc4, 0x01, 0x26, 0x12, 0x24, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x67, 0x52, 0x50, 0x43, 0x20, 0x41, 0x50, 0x49, 0x20, + 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x2e, 0x52, 0x03, 0x41, 0x50, 0x49, 0x12, 0x22, + 0x0a, 0x03, 0x47, 0x55, 0x49, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x2e, 0x47, 0x55, 0x49, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x03, 0x47, + 0x55, 0x49, 0x12, 0x1f, 0x0a, 0x02, 0x43, 0x41, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x43, 0x41, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, + 0x02, 0x43, 0x41, 0x12, 0x31, 0x0a, 0x08, 0x46, 0x72, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x64, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x46, 0x72, + 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x08, 0x46, 0x72, + 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x64, 0x12, 0x3d, 0x0a, 0x0e, 0x45, 0x78, 0x74, 0x72, 0x61, 0x46, + 0x72, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x64, 0x73, 0x18, 0x1f, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x46, 0x72, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x64, 0x43, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0e, 0x45, 0x78, 0x74, 0x72, 0x61, 0x46, 0x72, 0x6f, 0x6e, + 0x74, 0x65, 0x6e, 0x64, 0x73, 0x12, 0x34, 0x0a, 0x09, 0x44, 0x61, 0x74, 0x61, 0x73, 0x74, 0x6f, + 0x72, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x2e, 0x44, 0x61, 0x74, 0x61, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x52, 0x09, 0x44, 0x61, 0x74, 0x61, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x32, 0x0a, 0x09, 0x57, + 0x72, 0x69, 0x74, 0x65, 0x62, 0x61, 0x63, 0x6b, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x57, 0x72, 0x69, 0x74, 0x65, 0x62, 0x61, 0x63, 0x6b, + 0x42, 0x02, 0x18, 0x01, 0x52, 0x09, 0x57, 0x72, 0x69, 0x74, 0x65, 0x62, 0x61, 0x63, 0x6b, 0x12, + 0x25, 0x0a, 0x04, 0x4d, 0x61, 0x69, 0x6c, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4d, 0x61, 0x69, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x52, 0x04, 0x4d, 0x61, 0x69, 0x6c, 0x12, 0x2e, 0x0a, 0x07, 0x4c, 0x6f, 0x67, 0x67, 0x69, 0x6e, + 0x67, 0x18, 0x17, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, + 0x4c, 0x6f, 0x67, 0x67, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x07, 0x4c, + 0x6f, 0x67, 0x67, 0x69, 0x6e, 0x67, 0x12, 0x2b, 0x0a, 0x06, 0x4d, 0x69, 0x6e, 0x69, 0x6f, 0x6e, + 0x18, 0x28, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4d, + 0x69, 0x6e, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x4d, 0x69, 0x6e, + 0x69, 0x6f, 0x6e, 0x12, 0x40, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x65, 0x18, 0x14, + 0x20, 0x01, 0x28, 0x08, 0x42, 0x26, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, 0x20, 0x12, 0x1e, 0x45, 0x6e, + 0x61, 0x62, 0x6c, 0x65, 0x20, 0x76, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x65, 0x20, 0x6c, 0x6f, 0x67, + 0x67, 0x69, 0x6e, 0x67, 0x20, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x2e, 0x52, 0x07, 0x76, 0x65, + 0x72, 0x62, 0x6f, 0x73, 0x65, 0x12, 0x5c, 0x0a, 0x13, 0x61, 0x75, 0x74, 0x6f, 0x63, 0x65, 0x72, + 0x74, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x5f, 0x63, 0x61, 0x63, 0x68, 0x65, 0x18, 0x16, 0x20, 0x01, + 0x28, 0x09, 0x42, 0x2c, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, 0x26, 0x12, 0x24, 0x50, 0x61, 0x74, 0x68, + 0x20, 0x74, 0x6f, 0x20, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x20, 0x61, 0x75, 0x74, 0x6f, 0x63, 0x65, + 0x72, 0x74, 0x20, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x2e, + 0x52, 0x11, 0x61, 0x75, 0x74, 0x6f, 0x63, 0x65, 0x72, 0x74, 0x43, 0x65, 0x72, 0x74, 0x43, 0x61, + 0x63, 0x68, 0x65, 0x12, 0x6e, 0x0a, 0x0a, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, + 0x67, 0x18, 0x19, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, + 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x42, 0x35, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, 0x2f, 0x12, 0x2d, 0x57, 0x68, 0x65, 0x72, 0x65, 0x20, + 0x74, 0x6f, 0x20, 0x62, 0x69, 0x6e, 0x64, 0x20, 0x70, 0x72, 0x6f, 0x6d, 0x65, 0x74, 0x68, 0x65, + 0x75, 0x73, 0x20, 0x6d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x20, 0x65, 0x6e, + 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x2e, 0x52, 0x0a, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, + 0x69, 0x6e, 0x67, 0x12, 0x7f, 0x0a, 0x0a, 0x61, 0x70, 0x69, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x18, 0x1a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, + 0x41, 0x70, 0x69, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x42, + 0x48, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, 0x42, 0x12, 0x40, 0x49, 0x66, 0x20, 0x77, 0x65, 0x20, 0x6c, + 0x6f, 0x61, 0x64, 0x65, 0x64, 0x20, 0x74, 0x68, 0x65, 0x20, 0x61, 0x70, 0x69, 0x20, 0x63, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x20, 0x77, 0x65, 0x20, 0x6c, 0x6f, 0x61, 0x64, 0x20, 0x74, 0x68, 0x69, + 0x73, 0x20, 0x69, 0x6e, 0x74, 0x6f, 0x20, 0x74, 0x68, 0x65, 0x20, 0x67, 0x6c, 0x6f, 0x62, 0x61, + 0x6c, 0x20, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x52, 0x09, 0x61, 0x70, 0x69, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x12, 0x8f, 0x01, 0x0a, 0x08, 0x61, 0x75, 0x74, 0x6f, 0x65, 0x78, 0x65, + 0x63, 0x18, 0x1c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, + 0x41, 0x75, 0x74, 0x6f, 0x45, 0x78, 0x65, 0x63, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x42, 0x5c, + 0xe2, 0xfc, 0xe3, 0xc4, 0x01, 0x56, 0x12, 0x54, 0x49, 0x66, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, + 0x69, 0x73, 0x20, 0x73, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x65, 0x64, 0x20, 0x77, 0x65, 0x20, + 0x6c, 0x61, 0x75, 0x6e, 0x63, 0x68, 0x20, 0x74, 0x68, 0x65, 0x20, 0x62, 0x69, 0x6e, 0x61, 0x72, + 0x79, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x74, 0x68, 0x65, 0x20, 0x67, 0x69, 0x76, 0x65, 0x6e, + 0x20, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x20, 0x6c, 0x69, 0x6e, 0x65, 0x20, 0x61, 0x75, + 0x74, 0x6f, 0x6d, 0x61, 0x74, 0x69, 0x63, 0x61, 0x6c, 0x6c, 0x79, 0x2e, 0x52, 0x08, 0x61, 0x75, + 0x74, 0x6f, 0x65, 0x78, 0x65, 0x63, 0x12, 0x50, 0x0a, 0x0b, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, + 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x09, 0x42, 0x2f, 0xe2, 0xfc, 0xe3, + 0xc4, 0x01, 0x29, 0x12, 0x27, 0x54, 0x79, 0x70, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x73, 0x65, 0x72, + 0x76, 0x65, 0x72, 0x20, 0x28, 0x6c, 0x69, 0x6e, 0x75, 0x78, 0x2c, 0x20, 0x77, 0x69, 0x6e, 0x64, + 0x6f, 0x77, 0x73, 0x2c, 0x20, 0x64, 0x61, 0x72, 0x77, 0x69, 0x6e, 0x29, 0x52, 0x0a, 0x73, 0x65, + 0x72, 0x76, 0x65, 0x72, 0x54, 0x79, 0x70, 0x65, 0x12, 0x2b, 0x0a, 0x11, 0x6f, 0x62, 0x66, 0x75, + 0x73, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x18, 0x20, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x10, 0x6f, 0x62, 0x66, 0x75, 0x73, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x12, 0x2b, 0x0a, 0x08, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, + 0x73, 0x18, 0x21, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, + 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x73, 0x52, 0x08, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, + 0x74, 0x73, 0x12, 0x27, 0x0a, 0x0f, 0x61, 0x6e, 0x61, 0x6c, 0x79, 0x73, 0x69, 0x73, 0x5f, 0x74, + 0x61, 0x72, 0x67, 0x65, 0x74, 0x18, 0x22, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x61, 0x6e, 0x61, + 0x6c, 0x79, 0x73, 0x69, 0x73, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x12, 0x36, 0x0a, 0x0a, 0x72, + 0x65, 0x6d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x23, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x16, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x6d, 0x61, 0x70, 0x70, 0x69, 0x6e, + 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0a, 0x72, 0x65, 0x6d, 0x61, 0x70, 0x70, 0x69, + 0x6e, 0x67, 0x73, 0x12, 0x15, 0x0a, 0x06, 0x6f, 0x72, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x24, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x05, 0x6f, 0x72, 0x67, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x6f, 0x72, + 0x67, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x25, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6f, 0x72, + 0x67, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x64, 0x65, 0x62, 0x75, 0x67, 0x5f, 0x6d, + 0x6f, 0x64, 0x65, 0x18, 0x29, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x64, 0x65, 0x62, 0x75, 0x67, + 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x37, 0x0a, 0x08, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, + 0x18, 0x26, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, + 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x43, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x52, 0x08, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x12, 0x1a, 0x0a, + 0x08, 0x6c, 0x6f, 0x63, 0x6b, 0x64, 0x6f, 0x77, 0x6e, 0x18, 0x27, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x08, 0x6c, 0x6f, 0x63, 0x6b, 0x64, 0x6f, 0x77, 0x6e, 0x42, 0x34, 0x5a, 0x32, 0x77, 0x77, 0x77, + 0x2e, 0x76, 0x65, 0x6c, 0x6f, 0x63, 0x69, 0x64, 0x65, 0x78, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, + 0x6f, 0x6c, 0x61, 0x6e, 0x67, 0x2f, 0x76, 0x65, 0x6c, 0x6f, 0x63, 0x69, 0x72, 0x61, 0x70, 0x74, + 0x6f, 0x72, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/config/proto/config.proto b/config/proto/config.proto index 8f707fb567a..f7bb80bba9e 100644 --- a/config/proto/config.proto +++ b/config/proto/config.proto @@ -1098,9 +1098,17 @@ message Defaults { // its own but if this is set, we prevent any external access. bool disable_inventory_service_external_access = 34; - // Controls how exports work (creating hunt or colletion exports to a zip file). + // Controls how exports work (creating hunt or colletion exports + // to a zip file). int64 export_concurrency = 40; int64 export_max_timeout_sec = 41; + + // Watch plugin frequency sleep time in seconds: How often + // watch_syslog() will check for changes (default 3). + int64 watch_plugin_frequency = 44; + + // Maximum length of the line that will be parsed (16kb) + int64 watch_plugin_buffer_size = 45; } // Configures crypto preferences @@ -1281,7 +1289,7 @@ message Config { // does not know or use its own org id. string org_id = 36; string org_name = 37; - + bool debug_mode = 41; // The services that will run at initialization. Note this is not // set in the config file by the user but is propagated from the diff --git a/docs/references/sample_config/main.go b/docs/references/sample_config/main.go index 933d21a7193..a5db3877d76 100644 --- a/docs/references/sample_config/main.go +++ b/docs/references/sample_config/main.go @@ -106,6 +106,7 @@ var ( "defaults.max_batch_wait", "defaults.disable_inventory_service_external_access", "lockdown", + "debug_mode", } ) diff --git a/docs/references/server.config.yaml b/docs/references/server.config.yaml index 8b5e81268a9..f9189544eb1 100644 --- a/docs/references/server.config.yaml +++ b/docs/references/server.config.yaml @@ -506,7 +506,7 @@ GUI: - administrator # How long to keep the session alive between auth flows - default 24 hours - default_session_expiry_min: 8000 + default_session_expiry_min: 1440 # Used by the multi authenticator to provide multiple # authenticators. NOTE: Sub authenticators must be oauth based @@ -1188,6 +1188,21 @@ defaults: export_concurrency: 10 export_max_timeout_sec: 600 + # The server maintains an index of all hunts in order to quickly + # allow the GUI to filter/sort them. This setting controls how often + # to rebuild the hunt index (default 600 sec). You probably dont + # need to change it. + hunt_dispatcher_refresh_sec: 600 + + # Total number of cell versions we keep for undo/redo support. + notebook_versions: 5 + + # Watch plugin frequency sleep time in seconds: How often + # watch_syslog() will check for changes (default 3). + watch_plugin_frequency: 3 + + # Maximum length of the line that will be parsed (16kb) + watch_plugin_buffer_size: 16384 # The Velociraptor server may be placed into "lockdown" mode. While in # lockdown mode certain permissions are denied - even for @@ -1197,6 +1212,9 @@ defaults: # lockdown to false and restarting the server. lockdown: false +# This will be set when Velociraptor is started with the --debug flag. +debug_mode: false + # This configuration applies for minions. On minions this will # override the settings elsewhere in the config file allowing an easy # way to manage the difference between minions and master nodes. diff --git a/docs/references/vql.yaml b/docs/references/vql.yaml index 4e6eb67d5ee..e1372371836 100644 --- a/docs/references/vql.yaml +++ b/docs/references/vql.yaml @@ -1425,6 +1425,37 @@ tools on the client. https://docs.velociraptor.app/docs/extending_vql/#using-external-tools + + NOTE: The plugin receives an array of arguments which are passed + to the `execve()` system call as an array (on Windows they are + properly escaped into a command line). This means that you do not + need to escape or quote any special characters in the command. + + We noticed people often do this or variations on it: + ```vql + LET PathToCacls = "C:/Program Files" + LET CommandLine <= "cacls.exe " + '"' + PathToCacls + '"' + SELECT * FROM execve(argv=["powershell", "-c", CommandLine]) + ``` + + While this appears to work it is incorrect, fragile and + susceptible to a simple shell injection (for example if the + `PathToCacls` contains quotes). + + As a rule we prefer to not run commands through the shell at all + since it is not needed and unsafe. The correct approach is always + to split the `argv` into an array of distinct arguments: + + ```vql + LET PathToCacls = "C:/Program Files" + SELECT * FROM execve(argv=["cacls.exe", PathToCacls]) + ``` + + This calls the program directly and is not susceptible to escaping + or quoting issues (since there is no shell involved). Additionally + it does not invoke powershell which means that any execution + artifacts are not trampled by this VQL. + type: Plugin args: - name: argv @@ -6896,14 +6927,14 @@ - name: xattr description: | Query a file for the specified extended attribute. - - If no attributes are provided, this function will return all extended attributes + + If no attributes are provided, this function will return all extended attributes for the file. - + Please note: this API is not reliable, so please provided extended attributes where possible. - Note: This function only works on Mac and Linux. + Note: This function only works on Mac and Linux. type: Function args: - name: filename @@ -7057,4 +7088,3 @@ category: plugin metadata: permissions: FILESYSTEM_READ - diff --git a/server/comms.go b/server/comms.go index e928d2e49f5..de37f7436b4 100644 --- a/server/comms.go +++ b/server/comms.go @@ -27,6 +27,7 @@ import ( "math/rand" "net/http" "net/url" + "os" "sync/atomic" "time" @@ -47,10 +48,12 @@ import ( "www.velocidex.com/golang/velociraptor/constants" crypto_proto "www.velocidex.com/golang/velociraptor/crypto/proto" "www.velocidex.com/golang/velociraptor/logging" + http_utils "www.velocidex.com/golang/velociraptor/utils/http" ) var ( packetTooLargeError = errors.New("Packet too large!") + notFoundError = errors.New("Not Found") currentConnections = promauto.NewGauge(prometheus.GaugeOpts{ Name: "client_comms_current_connections", @@ -684,33 +687,12 @@ func send_client_messages(server_obj *Server) http.Handler { }) } -// Record the status of the request so we can log it. -type statusRecorder struct { - http.ResponseWriter - http.Flusher - status int - error []byte -} - -func (self *statusRecorder) WriteHeader(code int) { - self.status = code - self.ResponseWriter.WriteHeader(code) -} - -func (self *statusRecorder) Write(buf []byte) (int, error) { - if self.status == 500 { - self.error = buf - } - - return self.ResponseWriter.Write(buf) -} - func GetLoggingHandler(config_obj *config_proto.Config, handler string) func(http.Handler) http.Handler { logger := logging.GetLogger(config_obj, &logging.FrontendComponent) return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - rec := &statusRecorder{ + rec := &http_utils.StatusRecorder{ w, w.(http.Flusher), 200, nil} @@ -722,7 +704,7 @@ func GetLoggingHandler(config_obj *config_proto.Config, "url": r.URL.Path, "remote": r.RemoteAddr, "user-agent": r.UserAgent(), - "status": rec.status, + "status": rec.Status, "handler": handler, }).Info("Access to handler") }() @@ -740,7 +722,7 @@ func downloadPublic( // make sure the prefix is correct for i, p := range prefix { if len(components) <= i || p != components[i] { - returnError(w, 404, "Not Found") + returnError(config_obj, w, 404, notFoundError) return } } @@ -748,7 +730,7 @@ func downloadPublic( file_store_factory := file_store.GetFileStore(config_obj) fd, err := file_store_factory.ReadFile(path_spec) if err != nil { - returnError(w, 404, err.Error()) + returnError(config_obj, w, 404, err) return } @@ -764,9 +746,23 @@ func downloadPublic( }) } -func returnError(w http.ResponseWriter, code int, message string) { +func returnError( + config_obj *config_proto.Config, + w http.ResponseWriter, code int, err error) { w.WriteHeader(code) - _, _ = w.Write([]byte(html.EscapeString(message))) + + if config_obj.DebugMode { + _, _ = w.Write([]byte(html.EscapeString(err.Error()))) + return + } + + // In production provide generic errors. + if errors.Is(err, os.ErrNotExist) { + _, _ = w.Write([]byte("Not Found")) + + } else { + _, _ = w.Write([]byte("Error")) + } } // Calculate QPS diff --git a/server/metrics.go b/server/metrics.go index f155b53afbf..c03566c3058 100644 --- a/server/metrics.go +++ b/server/metrics.go @@ -6,6 +6,7 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" + http_utils "www.velocidex.com/golang/velociraptor/utils/http" ) var ( @@ -20,13 +21,13 @@ var ( func RecordHTTPStats(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - rec := &statusRecorder{ + rec := &http_utils.StatusRecorder{ w, w.(http.Flusher), 200, nil} next.ServeHTTP(rec, r) - status := fmt.Sprintf("%v", rec.status) + status := fmt.Sprintf("%v", rec.Status) httpErrorStatusCounters.WithLabelValues(status).Inc() }) } diff --git a/utils/http/logging.go b/utils/http/logging.go new file mode 100644 index 00000000000..6f73d1e0eb3 --- /dev/null +++ b/utils/http/logging.go @@ -0,0 +1,24 @@ +package http + +import "net/http" + +// Record the status of the request so we can log it. +type StatusRecorder struct { + http.ResponseWriter + http.Flusher + Status int + Error []byte +} + +func (self *StatusRecorder) WriteHeader(code int) { + self.Status = code + self.ResponseWriter.WriteHeader(code) +} + +func (self *StatusRecorder) Write(buf []byte) (int, error) { + if self.Status == 500 { + self.Error = buf + } + + return self.ResponseWriter.Write(buf) +} diff --git a/vql/parsers/json.go b/vql/parsers/json.go index 1d914d5efa1..1b92e6ea46f 100644 --- a/vql/parsers/json.go +++ b/vql/parsers/json.go @@ -31,11 +31,14 @@ import ( "google.golang.org/protobuf/reflect/protoreflect" "www.velocidex.com/golang/velociraptor/accessors" "www.velocidex.com/golang/velociraptor/acls" + "www.velocidex.com/golang/velociraptor/artifacts" + config_proto "www.velocidex.com/golang/velociraptor/config/proto" "www.velocidex.com/golang/velociraptor/json" utils "www.velocidex.com/golang/velociraptor/utils" "www.velocidex.com/golang/velociraptor/vql" vql_subsystem "www.velocidex.com/golang/velociraptor/vql" "www.velocidex.com/golang/velociraptor/vql/functions" + "www.velocidex.com/golang/velociraptor/vql/parsers/syslog" "www.velocidex.com/golang/vfilter" "www.velocidex.com/golang/vfilter/arg_parser" ) @@ -526,9 +529,10 @@ func (self _IndexAssociativeProtocol) GetMembers( } type WriteJSONPluginArgs struct { - Filename *accessors.OSPath `vfilter:"required,field=filename,doc=CSV files to open"` - Accessor string `vfilter:"optional,field=accessor,doc=The accessor to use"` - Query vfilter.StoredQuery `vfilter:"required,field=query,doc=query to write into the file."` + Filename *accessors.OSPath `vfilter:"required,field=filename,doc=CSV files to open"` + Accessor string `vfilter:"optional,field=accessor,doc=The accessor to use"` + Query vfilter.StoredQuery `vfilter:"required,field=query,doc=query to write into the file."` + BufferSize int `vfilter:"optional,field=buffer_size,doc=Maximum size of buffer before flushing to file."` } type WriteJSONPlugin struct{} @@ -549,6 +553,10 @@ func (self WriteJSONPlugin) Call( return } + if arg.BufferSize == 0 { + arg.BufferSize = BUFF_SIZE + } + var writer *bufio.Writer switch arg.Accessor { @@ -575,7 +583,7 @@ func (self WriteJSONPlugin) Call( } defer file.Close() - writer = bufio.NewWriterSize(file, BUFF_SIZE) + writer = bufio.NewWriterSize(file, arg.BufferSize) defer writer.Flush() default: @@ -613,6 +621,96 @@ func (self WriteJSONPlugin) Info(scope vfilter.Scope, type_map *vfilter.TypeMap) } } +type WatchJsonlPlugin struct{} + +func (self WatchJsonlPlugin) Info(scope vfilter.Scope, type_map *vfilter.TypeMap) *vfilter.PluginInfo { + return &vfilter.PluginInfo{ + Name: "watch_jsonl", + Doc: "Watch a jsonl file and stream events from it.", + ArgType: type_map.AddType(scope, &syslog.ScannerPluginArgs{}), + Metadata: vql.VQLMetadata().Permissions(acls.FILESYSTEM_READ).Build(), + } +} + +func (self WatchJsonlPlugin) Call( + ctx context.Context, scope vfilter.Scope, + args *ordereddict.Dict) <-chan vfilter.Row { + output_chan := make(chan vfilter.Row) + + go func() { + defer close(output_chan) + arg := &syslog.ScannerPluginArgs{} + err := arg_parser.ExtractArgsWithContext(ctx, scope, args, arg) + if err != nil { + scope.Log("watch_jsonl: %v", err) + return + } + + err = vql_subsystem.CheckFilesystemAccess(scope, arg.Accessor) + if err != nil { + scope.Log("watch_jsonl: %v", err) + return + } + + // This plugin needs to be running on clients which have no + // server config object. + client_config_obj, ok := artifacts.GetConfig(scope) + if !ok { + scope.Log("watch_jsonl: unable to get config") + return + } + + config_obj := &config_proto.Config{Client: client_config_obj} + + event_channel := make(chan vfilter.Row) + + // Register the output channel as a listener to the + // global event. + for _, filename := range arg.Filenames { + cancel := syslog.GlobalSyslogService(config_obj).Register( + filename, arg.Accessor, ctx, scope, + event_channel) + + defer cancel() + } + + // Wait until the query is complete. + for { + select { + case <-ctx.Done(): + return + + case event, ok := <-event_channel: + if !ok { + return + } + + // Get the line from the event + line := vql_subsystem.GetStringFromRow(scope, event, "Line") + if line == "" { + continue + } + + json_event := ordereddict.NewDict() + err := json.Unmarshal([]byte(line), json_event) + if err != nil { + scope.Log("Invalid jsonl: %v\n", line) + continue + } + + select { + case <-ctx.Done(): + return + + case output_chan <- json_event: + } + } + } + }() + + return output_chan +} + func init() { vql_subsystem.RegisterFunction(&ParseJsonFunction{}) vql_subsystem.RegisterFunction(&ParseJsonArray{}) @@ -623,4 +721,5 @@ func init() { vql_subsystem.RegisterPlugin(&ParseJsonArrayPlugin{}) vql_subsystem.RegisterPlugin(&ParseJsonlPlugin{}) vql_subsystem.RegisterPlugin(&WriteJSONPlugin{}) + vql_subsystem.RegisterPlugin(&WatchJsonlPlugin{}) } diff --git a/vql/parsers/syslog/auditd.go b/vql/parsers/syslog/auditd.go index 30e0215da25..50666584b87 100644 --- a/vql/parsers/syslog/auditd.go +++ b/vql/parsers/syslog/auditd.go @@ -150,7 +150,7 @@ func (self WatchAuditdPlugin) Call( } }() - scanner := _WatchSyslogPlugin{} + scanner := WatchSyslogPlugin{} for row := range scanner.Call(ctx, scope, args) { line, pres := scope.Associative(row, "Line") if !pres { diff --git a/vql/parsers/syslog/fixtures/TestSyslogReader.golden b/vql/parsers/syslog/fixtures/TestSyslogReader.golden new file mode 100644 index 00000000000..a657fdcaa50 --- /dev/null +++ b/vql/parsers/syslog/fixtures/TestSyslogReader.golden @@ -0,0 +1,59 @@ +{ + "Short Lines": [ + { + "Line": "0123456701" + }, + { + "Line": "0123456702" + }, + { + "Line": "0123456703" + } + ], + "Long Lines": [ + { + "Line": "01234567890123456701" + }, + { + "Line": "01234567890123456702" + }, + { + "Line": "01234567890123456703" + } + ], + "Broken Line": [ + { + "Line": "0123456no crlfand this is crlf" + } + ], + "Broken Buffer": [ + { + "Line": "01234567890123456701,01234567890123456702,01234567" + }, + { + "Line": "890123456703" + } + ], + "Lines after file rewind": [ + { + "Line": "0123456701" + }, + { + "Line": "0123456702" + }, + { + "Line": "0123456703" + } + ], + "Empty lines": [ + { + "Line": "" + }, + { + "Line": "" + }, + { + "Line": "" + } + ] +} \ No newline at end of file diff --git a/vql/parsers/syslog/scanner.go b/vql/parsers/syslog/scanner.go index 789840b91cc..93ac97e187b 100644 --- a/vql/parsers/syslog/scanner.go +++ b/vql/parsers/syslog/scanner.go @@ -94,9 +94,9 @@ func (self ScannerPlugin) Call( return output_chan } -type _WatchSyslogPlugin struct{} +type WatchSyslogPlugin struct{} -func (self _WatchSyslogPlugin) Call( +func (self WatchSyslogPlugin) Call( ctx context.Context, scope vfilter.Scope, args *ordereddict.Dict) <-chan vfilter.Row { @@ -160,7 +160,7 @@ func (self _WatchSyslogPlugin) Call( return output_chan } -func (self _WatchSyslogPlugin) Info(scope vfilter.Scope, type_map *vfilter.TypeMap) *vfilter.PluginInfo { +func (self WatchSyslogPlugin) Info(scope vfilter.Scope, type_map *vfilter.TypeMap) *vfilter.PluginInfo { return &vfilter.PluginInfo{ Name: "watch_syslog", Doc: "Watch a syslog file and stream events from it. ", @@ -193,6 +193,6 @@ func maybeOpenGzip(scope vfilter.Scope, } func init() { - vql_subsystem.RegisterPlugin(&_WatchSyslogPlugin{}) + vql_subsystem.RegisterPlugin(&WatchSyslogPlugin{}) vql_subsystem.RegisterPlugin(&ScannerPlugin{}) } diff --git a/vql/parsers/syslog/watcher.go b/vql/parsers/syslog/watcher.go index 625abc54f44..97e93ad5669 100644 --- a/vql/parsers/syslog/watcher.go +++ b/vql/parsers/syslog/watcher.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "io" + "os" "sync" "time" @@ -16,11 +17,6 @@ import ( "www.velocidex.com/golang/vfilter" ) -const ( - FREQUENCY = 3 * time.Second - BUFFER_SIZE = 16 * 1024 -) - var ( mu sync.Mutex gSyslogService *SyslogWatcherService @@ -43,10 +39,30 @@ type SyslogWatcherService struct { config_obj *config_proto.Config registrations map[string][]*Handle + + sleep_time time.Duration + buffer_size int64 + + monitor_count int } func NewSyslogWatcherService(config_obj *config_proto.Config) *SyslogWatcherService { + + sleep_time := 3 * time.Second + buffer_size := int64(16 * 1024) + if config_obj.Defaults != nil { + if config_obj.Defaults.WatchPluginFrequency > 0 { + sleep_time = time.Second * time.Duration( + config_obj.Defaults.WatchPluginFrequency) + } + if config_obj.Defaults.WatchPluginBufferSize > 0 { + buffer_size = config_obj.Defaults.WatchPluginBufferSize + } + } + return &SyslogWatcherService{ + sleep_time: sleep_time, + buffer_size: buffer_size, config_obj: config_obj, registrations: make(map[string][]*Handle), } @@ -126,7 +142,7 @@ func (self *SyslogWatcherService) StartMonitoring( cursor = self.monitorOnce(filename, accessor_name, accessor, cursor) - time.Sleep(FREQUENCY) + time.Sleep(self.sleep_time) } } @@ -148,7 +164,7 @@ func (self *SyslogWatcherService) findLastLineOffset( defer fd.Close() cursor.file_size = stat.Size() - last_offset := cursor.file_size - BUFFER_SIZE + last_offset := cursor.file_size - self.buffer_size if last_offset < 0 { last_offset = 0 } @@ -159,7 +175,7 @@ func (self *SyslogWatcherService) findLastLineOffset( return cursor } - buff := make([]byte, BUFFER_SIZE) + buff := make([]byte, self.buffer_size) n, err := fd.Read(buff) if err != nil && err != io.EOF { return cursor @@ -182,7 +198,10 @@ func (self *SyslogWatcherService) monitorOnce( cursor *Cursor) *Cursor { self.mu.Lock() - defer self.mu.Unlock() + defer func() { + self.monitor_count++ + self.mu.Unlock() + }() stat, err := accessor.LstatWithOSPath(filename) if err != nil { @@ -198,7 +217,7 @@ func (self *SyslogWatcherService) monitorOnce( // file was truncated. if stat.Size() < cursor.file_size { logger := logging.GetLogger(self.config_obj, &logging.ClientComponent) - logger.Info("File size (%v) is smaller than last know size (%v) - assuming file was truncated. Will start reading at the start again.", + logger.Info("File size (%v) is smaller than last known size (%v) - assuming file was truncated. Will start reading at the start again.", stat.Size(), cursor.file_size) cursor.last_line_offset = 0 } @@ -217,55 +236,86 @@ func (self *SyslogWatcherService) monitorOnce( } defer fd.Close() - // File must be seekable - pos, err := fd.Seek(cursor.last_line_offset, 0) - if err != nil { - return cursor - } + for { + // Only read up to the last offset in case the file grows more. + to_read := cursor.file_size - cursor.last_line_offset + if to_read > self.buffer_size { + to_read = self.buffer_size + } - // File is truncated, start reading from the front again. - if cursor.last_line_offset != pos { - cursor.last_line_offset = 0 - return cursor - } + if to_read == 0 { + return cursor + } - buff := make([]byte, BUFFER_SIZE) - n, err := fd.Read(buff) - if err != nil && err != io.EOF { - return cursor - } + // File must be seekable + pos, err := fd.Seek(cursor.last_line_offset, os.SEEK_SET) + if err != nil { + return cursor + } - buff = buff[:n] - offset := 0 - - // Read whole lines inside the buffer - for len(buff)-offset > 0 { - new_lf := bytes.IndexByte(buff[offset:], '\n') - if new_lf > 0 { - new_handles := self.distributeLine( - string(buff[offset:offset+new_lf]), filename, key, handles) - - // No more listeners - we dont care any more. - if len(new_handles) == 0 { - delete(self.registrations, key) + // File is truncated, start reading from the front again. + if cursor.last_line_offset != pos { + cursor.last_line_offset = 0 + return cursor + } + + buff := make([]byte, to_read) + n, err := fd.Read(buff) + if err != nil && err != io.EOF { + return cursor + } + + buff = buff[:n] + + // Read whole lines inside the buffer + for len(buff) > 0 { + new_lf := bytes.IndexByte(buff, '\n') + if new_lf >= 0 { cursor.last_line_offset += int64(new_lf) + 1 - return cursor + + handles = self.distributeLine( + string(buff[:new_lf]), filename, key, handles) + + // No more listeners - we dont care any more. + if len(handles) == 0 { + return cursor + } + + // Advance the cursor to the next line + buff = buff[new_lf+1:] + + // Get next line + continue } - // Update the registrations - possibly - // omitting finished listeners. - self.registrations[key] = new_handles - handles = new_handles + // The entire buffer does not contain lf at all - we skip + // the entire buffer and hope to get a line on the next + // read. + if len(buff) == n { - cursor.last_line_offset += int64(new_lf) + 1 - offset += new_lf + 1 + // The last line is just too long for the buffer. We + // break it up into multiple lines to fit. + if cursor.file_size-cursor.last_line_offset > + self.buffer_size { + cursor.last_line_offset += int64(len(buff)) - } else { - // The buffer does not contain lf at all - we - // skip the entire buffer and hope to get a - // line on the next read. - cursor.last_line_offset += int64(len(buff) - offset) - return cursor + handles = self.distributeLine( + string(buff), filename, key, handles) + + // No more listeners - we dont care any more. + if len(handles) == 0 { + return cursor + } + + // Drop the buffer and read some more. + break + } + + return cursor + } + + // Abandon this buffer and try again + break } } return cursor @@ -290,6 +340,13 @@ func (self *SyslogWatcherService) distributeLine( } } + // Update the registrations - possibly omitting finished + // listeners. + if len(new_handles) == 0 { + delete(self.registrations, key) + } + self.registrations[key] = new_handles + return new_handles } diff --git a/vql/parsers/syslog/watcher_test.go b/vql/parsers/syslog/watcher_test.go new file mode 100644 index 00000000000..066c73ae2ca --- /dev/null +++ b/vql/parsers/syslog/watcher_test.go @@ -0,0 +1,302 @@ +package syslog + +import ( + "context" + "io/ioutil" + "os" + "sync" + "testing" + "time" + + "github.com/Velocidex/ordereddict" + "github.com/sebdah/goldie" + "github.com/stretchr/testify/suite" + "www.velocidex.com/golang/velociraptor/accessors" + config_proto "www.velocidex.com/golang/velociraptor/config/proto" + "www.velocidex.com/golang/velociraptor/file_store/test_utils" + "www.velocidex.com/golang/velociraptor/json" + "www.velocidex.com/golang/velociraptor/logging" + "www.velocidex.com/golang/velociraptor/services" + "www.velocidex.com/golang/velociraptor/vql/acl_managers" + "www.velocidex.com/golang/velociraptor/vtesting" + "www.velocidex.com/golang/velociraptor/vtesting/assert" + "www.velocidex.com/golang/vfilter" + + _ "www.velocidex.com/golang/velociraptor/accessors/file" +) + +type SyslogWatcherTestSuite struct { + test_utils.TestSuite + + mu sync.Mutex + result []vfilter.Row + output_chan chan vfilter.Row + scope vfilter.Scope + temp_file string + filename *accessors.OSPath + wg sync.WaitGroup + cancel func() +} + +func (self *SyslogWatcherTestSuite) SetupTest() { + self.TestSuite.SetupTest() + + // Change the normal frequency to very long so it does not + // interfere with our test. + self.ConfigObj.Defaults = &config_proto.Defaults{ + WatchPluginFrequency: 10000, + + // Make the buffer small so we can test exceeding it + WatchPluginBufferSize: 50, + } + + self.output_chan = make(chan vfilter.Row) + + ctx, cancel := context.WithCancel(self.Ctx) + self.cancel = cancel + + self.wg.Add(1) + go func() { + defer self.wg.Done() + defer close(self.output_chan) + + for { + select { + case <-ctx.Done(): + return + + case item, ok := <-self.output_chan: + if !ok { + return + } + self.mu.Lock() + self.result = append(self.result, item) + self.mu.Unlock() + } + } + }() + + builder := services.ScopeBuilder{ + Config: self.ConfigObj, + ACLManager: acl_managers.NullACLManager{}, + Logger: logging.NewPlainLogger( + self.ConfigObj, &logging.FrontendComponent), + Env: ordereddict.NewDict(), + } + + manager, err := services.GetRepositoryManager(self.ConfigObj) + assert.NoError(self.T(), err) + + self.scope = manager.BuildScope(builder) + + fd, err := ioutil.TempFile("", "tmp") + assert.NoError(self.T(), err) + fd.Close() + + self.temp_file = fd.Name() + + self.truncateFile() + + self.filename, err = accessors.NewGenericOSPath(self.temp_file) + assert.NoError(self.T(), err) +} + +func (self *SyslogWatcherTestSuite) TearDownTest() { + self.cancel() + self.wg.Wait() +} + +func (self *SyslogWatcherTestSuite) truncateFile() { + fd, err := os.OpenFile(self.temp_file, + os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600) + assert.NoError(self.T(), err) + fd.Close() +} + +func (self *SyslogWatcherTestSuite) appendData(data string) { + fd, err := os.OpenFile(self.temp_file, + os.O_RDWR|os.O_CREATE, 0600) + assert.NoError(self.T(), err) + defer fd.Close() + + fd.Seek(0, os.SEEK_END) + + _, err = fd.Write([]byte(data)) + assert.NoError(self.T(), err) +} + +func (self *SyslogWatcherTestSuite) clearLines() { + self.mu.Lock() + defer self.mu.Unlock() + self.result = nil +} + +func (self *SyslogWatcherTestSuite) getLines() []vfilter.Row { + self.mu.Lock() + defer self.mu.Unlock() + result := []vfilter.Row{} + for _, i := range self.result { + result = append(result, i) + } + return result +} + +func (self *SyslogWatcherTestSuite) TestSyslogReader() { + service := NewSyslogWatcherService(self.ConfigObj) + + golden := ordereddict.NewDict() + + // Register a watcher + accessor, err := accessors.GetAccessor("file", self.scope) + assert.NoError(self.T(), err) + + ctx := context.Background() + closer := service.Register(self.filename, "file", + ctx, self.scope, self.output_chan) + defer closer() + + // Registering a watcher will scan the file once. We need to wait + // until it is over before we start the test. + vtesting.WaitUntil(time.Second, self.T(), func() bool { + service.mu.Lock() + defer service.mu.Unlock() + + return service.monitor_count > 0 + }) + + // Find the last line + cursor := service.findLastLineOffset(self.filename, accessor) + assert.Equal(self.T(), cursor.last_line_offset, int64(0)) + + self.appendData("This is the first line\nSecond Line\n") + + // Find the last line again + cursor = service.findLastLineOffset(self.filename, accessor) + assert.Equal(self.T(), cursor.last_line_offset, int64(35)) + assert.Equal(self.T(), cursor.file_size, int64(35)) + + // Pull the next line off - no new data + cursor = service.monitorOnce(self.filename, "file", accessor, cursor) + assert.Equal(self.T(), cursor.last_line_offset, int64(35)) + + // Write 3 short full lines (10 bytes + \n * 3 = 33 bytes). This + // should fit in a single buffer read (50 bytes) + self.appendData("0123456701\n0123456702\n0123456703\n") + + cursor = service.monitorOnce(self.filename, "file", accessor, cursor) + + // Make sure we consumed all the data since it ends on \n + assert.Equal(self.T(), cursor.last_line_offset, cursor.file_size) + + // Wait for 3 messages to be distributed + vtesting.WaitUntil(time.Second, self.T(), func() bool { + return len(self.getLines()) == 3 + }) + + golden.Set("Short Lines", self.getLines()) + + self.clearLines() + + // Write 3 longer full lines (20 bytes + \n * 3 = 63 bytes). While + // each line should fit in the buffer (50 bytes) the whole data + // since the last cursor will not at once. + self.appendData("01234567890123456701\n01234567890123456702\n01234567890123456703\n") + + cursor = service.monitorOnce(self.filename, "file", accessor, cursor) + assert.Equal(self.T(), cursor.last_line_offset, cursor.file_size) + + // Wait for 3 messages to be distributed + vtesting.WaitUntil(time.Second, self.T(), func() bool { + return len(self.getLines()) == 3 + }) + + golden.Set("Long Lines", self.getLines()) + + // Write data with no crlf should not advance cursor. + // each line should fit in the buffer (50 bytes) the whole data + // since the last cursor will not at once. + self.appendData("0123456no crlf") + + self.clearLines() + + cursor = service.monitorOnce(self.filename, "file", accessor, cursor) + + // Cursor has not consumed the last line because there is no \n + assert.True(self.T(), cursor.last_line_offset < cursor.file_size) + + self.appendData("and this is crlf\n") + + cursor = service.monitorOnce(self.filename, "file", accessor, cursor) + + // All data is consumed + assert.Equal(self.T(), cursor.last_line_offset, cursor.file_size) + + // Wait for 3 messages to be distributed + vtesting.WaitUntil(time.Second, self.T(), func() bool { + return len(self.getLines()) == 1 + }) + + golden.Set("Broken Line", self.getLines()) + + self.clearLines() + + // Now send a single line which is too long to fit in the + // buffer. Total length 63 bytes + self.appendData("01234567890123456701,01234567890123456702,01234567890123456703\n") + + // We still read all the way to the end but we split the data over + // multiple lines. + cursor = service.monitorOnce(self.filename, "file", accessor, cursor) + assert.Equal(self.T(), cursor.last_line_offset, cursor.file_size) + + // Message is broken across multiple lines because it is too long + // to fit in one buffer. + vtesting.WaitUntil(time.Second, self.T(), func() bool { + return len(self.getLines()) == 2 + }) + + golden.Set("Broken Buffer", self.getLines()) + + // Now test file truncation. + self.truncateFile() + self.clearLines() + + // Write 3 short full lines (10 bytes + \n * 3 = 33 bytes). This + // should fit in a single buffer read (50 bytes) + self.appendData("0123456701\n0123456702\n0123456703\n") + + // The new curser is rewinded to the start of the file. + old_curser := *cursor + new_cursor := service.monitorOnce(self.filename, "file", accessor, cursor) + + assert.True(self.T(), new_cursor.last_line_offset < old_curser.last_line_offset) + assert.Equal(self.T(), new_cursor.last_line_offset, new_cursor.file_size) + + vtesting.WaitUntil(time.Second, self.T(), func() bool { + return len(self.getLines()) == 3 + }) + + golden.Set("Lines after file rewind", self.getLines()) + + self.clearLines() + + // Now write empty lines. + self.appendData("\n\n\n") + + // Lines are consumed + cursor = service.monitorOnce(self.filename, "file", accessor, cursor) + assert.Equal(self.T(), cursor.last_line_offset, cursor.file_size) + + vtesting.WaitUntil(time.Second, self.T(), func() bool { + return len(self.getLines()) == 3 + }) + + golden.Set("Empty lines", self.getLines()) + + goldie.Assert(self.T(), "TestSyslogReader", + json.MustMarshalIndent(golden)) +} + +func TestSyslogWatcher(t *testing.T) { + suite.Run(t, &SyslogWatcherTestSuite{}) +}