From 8b6ac25b1ffd62a9aef1b2bf3fbacc98d1e8caf9 Mon Sep 17 00:00:00 2001 From: Aleksandr Maus Date: Tue, 6 Apr 2021 08:47:17 -0400 Subject: [PATCH] Agent Actions: Part 1 of Osquerybeat with Agent actions (#24507) (#24675) Bakport of https://github.com/elastic/beats/pull/24507 to 7.x (cherry picked from commit 0b4e053f5d954b1bfa960cc1800e23d2c4ee6b28) --- NOTICE.txt | 16 ++-- go.mod | 8 +- go.sum | 13 +-- libbeat/management/management.go | 15 ++++ x-pack/elastic-agent/.gitignore | 3 + .../gateway/fleet/fleet_gateway.go | 2 +- .../handlers/handler_action_application.go | 46 ++++++++++- .../elastic-agent/pkg/agent/program/spec.go | 1 + .../pkg/core/plugin/process/start.go | 2 + .../elastic-agent/pkg/core/server/server.go | 30 +++++++ .../pkg/core/server/server_test.go | 14 +++- x-pack/elastic-agent/pkg/fleetapi/ack_cmd.go | 5 ++ .../pkg/fleetapi/acker/fleet/fleet_acker.go | 10 ++- x-pack/elastic-agent/pkg/fleetapi/action.go | 46 ++++++----- .../elastic-agent/pkg/fleetapi/action_test.go | 82 +++++++++++++++++++ x-pack/libbeat/management/fleet/manager.go | 17 +++- x-pack/libbeat/management/manager.go | 7 ++ 17 files changed, 271 insertions(+), 46 deletions(-) create mode 100644 x-pack/elastic-agent/pkg/fleetapi/action_test.go diff --git a/NOTICE.txt b/NOTICE.txt index ed4f2cb3423..8e77c3bfe69 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -6103,11 +6103,11 @@ Contents of probable licence file $GOMODCACHE/github.com/elastic/ecs@v1.8.0/LICE -------------------------------------------------------------------------------- Dependency : github.com/elastic/elastic-agent-client/v7 -Version: v7.0.0-20200709172729-d43b7ad5833a +Version: v7.0.0-20210308165121-7dd05ee2b5a5 Licence type (autodetected): Elastic -------------------------------------------------------------------------------- -Contents of probable licence file $GOMODCACHE/github.com/elastic/elastic-agent-client/v7@v7.0.0-20200709172729-d43b7ad5833a/LICENSE.txt: +Contents of probable licence file $GOMODCACHE/github.com/elastic/elastic-agent-client/v7@v7.0.0-20210308165121-7dd05ee2b5a5/LICENSE.txt: ELASTIC LICENSE AGREEMENT @@ -9446,11 +9446,11 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- Dependency : github.com/golang/protobuf -Version: v1.4.2 +Version: v1.4.3 Licence type (autodetected): BSD-3-Clause -------------------------------------------------------------------------------- -Contents of probable licence file $GOMODCACHE/github.com/golang/protobuf@v1.4.2/LICENSE: +Contents of probable licence file $GOMODCACHE/github.com/golang/protobuf@v1.4.3/LICENSE: Copyright 2010 The Go Authors. All rights reserved. @@ -9995,11 +9995,11 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- Dependency : github.com/google/uuid -Version: v1.1.2-0.20190416172445-c2e93f3ae59f +Version: v1.1.2 Licence type (autodetected): BSD-3-Clause -------------------------------------------------------------------------------- -Contents of probable licence file $GOMODCACHE/github.com/google/uuid@v1.1.2-0.20190416172445-c2e93f3ae59f/LICENSE: +Contents of probable licence file $GOMODCACHE/github.com/google/uuid@v1.1.2/LICENSE: Copyright (c) 2009,2014 Google Inc. All rights reserved. @@ -12505,11 +12505,11 @@ THE SOFTWARE. -------------------------------------------------------------------------------- Dependency : github.com/mitchellh/mapstructure -Version: v1.1.2 +Version: v1.3.3 Licence type (autodetected): MIT -------------------------------------------------------------------------------- -Contents of probable licence file $GOMODCACHE/github.com/mitchellh/mapstructure@v1.1.2/LICENSE: +Contents of probable licence file $GOMODCACHE/github.com/mitchellh/mapstructure@v1.3.3/LICENSE: The MIT License (MIT) diff --git a/go.mod b/go.mod index e8855bce0fc..41b5ea3c7e3 100644 --- a/go.mod +++ b/go.mod @@ -60,7 +60,7 @@ require ( github.com/eapache/go-resiliency v1.2.0 github.com/eclipse/paho.mqtt.golang v1.2.1-0.20200121105743-0d940dd29fd2 github.com/elastic/ecs v1.8.0 - github.com/elastic/elastic-agent-client/v7 v7.0.0-20200709172729-d43b7ad5833a + github.com/elastic/elastic-agent-client/v7 v7.0.0-20210308165121-7dd05ee2b5a5 github.com/elastic/go-concert v0.1.0 github.com/elastic/go-libaudit/v2 v2.2.0 github.com/elastic/go-licenser v0.3.1 @@ -87,13 +87,13 @@ require ( github.com/gofrs/flock v0.7.2-0.20190320160742-5135e617513b github.com/gofrs/uuid v3.3.0+incompatible github.com/gogo/protobuf v1.3.1 - github.com/golang/protobuf v1.4.2 + github.com/golang/protobuf v1.4.3 github.com/golang/snappy v0.0.1 github.com/gomodule/redigo v1.8.3 github.com/google/flatbuffers v1.7.2-0.20170925184458-7a6b2bf521e9 github.com/google/go-cmp v0.5.2 github.com/google/gopacket v1.1.18-0.20191009163724-0ad7f2610e34 - github.com/google/uuid v1.1.2-0.20190416172445-c2e93f3ae59f + github.com/google/uuid v1.1.2 github.com/gorhill/cronexpr v0.0.0-20180427100037-88b0669f7d75 github.com/gorilla/mux v1.7.2 // indirect github.com/grpc-ecosystem/grpc-gateway v1.13.0 // indirect @@ -121,7 +121,7 @@ require ( github.com/miekg/dns v1.1.15 github.com/mitchellh/gox v1.0.1 github.com/mitchellh/hashstructure v0.0.0-20170116052023-ab25296c0f51 - github.com/mitchellh/mapstructure v1.1.2 + github.com/mitchellh/mapstructure v1.3.3 github.com/morikuni/aec v1.0.0 // indirect github.com/oklog/ulid v1.3.1 github.com/opencontainers/go-digest v1.0.0-rc1.0.20190228220655-ac19fd6e7483 // indirect diff --git a/go.sum b/go.sum index 7eef9ed3806..ceb4f84f463 100644 --- a/go.sum +++ b/go.sum @@ -245,8 +245,8 @@ github.com/elastic/dhcp v0.0.0-20200227161230-57ec251c7eb3 h1:lnDkqiRFKm0rxdljqr github.com/elastic/dhcp v0.0.0-20200227161230-57ec251c7eb3/go.mod h1:aPqzac6AYkipvp4hufTyMj5PDIphF3+At8zr7r51xjY= github.com/elastic/ecs v1.8.0 h1:wa61IDQsQcZyJa6hwbhqGO+631H+kGHhe0J4V7tMPZY= github.com/elastic/ecs v1.8.0/go.mod h1:pgiLbQsijLOJvFR8OTILLu0Ni/R/foUNg0L+T6mU9b4= -github.com/elastic/elastic-agent-client/v7 v7.0.0-20200709172729-d43b7ad5833a h1:2NHgf1RUw+f240lpTnLrCp1aBNvq2wDi0E1A423/S1k= -github.com/elastic/elastic-agent-client/v7 v7.0.0-20200709172729-d43b7ad5833a/go.mod h1:uh/Gj9a0XEbYoM4NYz4LvaBVARz3QXLmlNjsrKY9fTc= +github.com/elastic/elastic-agent-client/v7 v7.0.0-20210308165121-7dd05ee2b5a5 h1:n4VHMzwk4o8+0zTCDej1M6uUR9rkzScpSeZXi0B8y1w= +github.com/elastic/elastic-agent-client/v7 v7.0.0-20210308165121-7dd05ee2b5a5/go.mod h1:uh/Gj9a0XEbYoM4NYz4LvaBVARz3QXLmlNjsrKY9fTc= github.com/elastic/fsevents v0.0.0-20181029231046-e1d381a4d270 h1:cWPqxlPtir4RoQVCpGSRXmLqjEHpJKbR60rxh1nQZY4= github.com/elastic/fsevents v0.0.0-20181029231046-e1d381a4d270/go.mod h1:Msl1pdboCbArMF/nSCDUXgQuWTeoMmE/z8607X+k7ng= github.com/elastic/go-concert v0.1.0 h1:gz/yvA3bseuHzoF/lNMltkL30XdPqMo+bg5o2mBx2EE= @@ -366,6 +366,8 @@ github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvq github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/gomodule/redigo v1.8.3 h1:HR0kYDX2RJZvAup8CsiJwxB4dTCSC0AaUq6S4SiLwUc= @@ -394,8 +396,8 @@ github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm4 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.1.2-0.20190416172445-c2e93f3ae59f h1:XXzyYlFbxK3kWfcmu3Wc+Tv8/QQl/VqwsWuSYF1Rj0s= -github.com/google/uuid v1.1.2-0.20190416172445-c2e93f3ae59f/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= @@ -541,8 +543,9 @@ github.com/mitchellh/hashstructure v0.0.0-20170116052023-ab25296c0f51 h1:qdHlMll github.com/mitchellh/hashstructure v0.0.0-20170116052023-ab25296c0f51/go.mod h1:QjSHrPWS+BGUVBYkbTZWEnOh3G1DutKwClXU/ABz6AQ= github.com/mitchellh/iochan v1.0.0 h1:C+X3KsSTLFVBr/tK1eYN/vs4rJcvsiLU338UhYPJWeY= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= -github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.3.3 h1:SzB1nHZ2Xi+17FP0zVQBHIZqvwRN9408fJO8h+eeNA8= +github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= diff --git a/libbeat/management/management.go b/libbeat/management/management.go index 509d2978eae..b29dd74a3c9 100644 --- a/libbeat/management/management.go +++ b/libbeat/management/management.go @@ -26,6 +26,7 @@ import ( "github.com/elastic/beats/v7/libbeat/common/reload" "github.com/elastic/beats/v7/libbeat/feature" "github.com/elastic/beats/v7/libbeat/logp" + "github.com/elastic/elastic-agent-client/v7/pkg/client" ) // Status describes the current status of the beat. @@ -82,6 +83,14 @@ type Manager interface { // CheckRawConfig check settings are correct before launching the beat. CheckRawConfig(cfg *common.Config) error + + // RegisterAction registers action handler with the client + RegisterAction(action client.Action) + // UnregisterAction unregisters action handler with the client + UnregisterAction(action client.Action) + + // SetPayload sets the client payload + SetPayload(map[string]interface{}) } // PluginFunc for creating FactoryFunc if it matches a config @@ -155,3 +164,9 @@ func (n *nilManager) UpdateStatus(status Status, msg string) { n.logger.Infof("Status change to %s: %s", status, msg) } } + +func (n *nilManager) RegisterAction(action client.Action) {} + +func (n *nilManager) UnregisterAction(action client.Action) {} + +func (n *nilManager) SetPayload(map[string]interface{}) {} diff --git a/x-pack/elastic-agent/.gitignore b/x-pack/elastic-agent/.gitignore index 81ce70aa69b..cd297650b08 100644 --- a/x-pack/elastic-agent/.gitignore +++ b/x-pack/elastic-agent/.gitignore @@ -7,3 +7,6 @@ pkg/agent/operation/tests/scripts/configurable-1.0-darwin-x86/configurable pkg/agent/operation/tests/scripts/servicable-1.0-darwin-x86/configurable pkg/agent/transpiler/tests/exec-1.0-darwin-x86_64/exec pkg/agent/application/fleet.yml + +# VSCode +/.vscode diff --git a/x-pack/elastic-agent/pkg/agent/application/gateway/fleet/fleet_gateway.go b/x-pack/elastic-agent/pkg/agent/application/gateway/fleet/fleet_gateway.go index a6ae917eaad..10c04ed6069 100644 --- a/x-pack/elastic-agent/pkg/agent/application/gateway/fleet/fleet_gateway.go +++ b/x-pack/elastic-agent/pkg/agent/application/gateway/fleet/fleet_gateway.go @@ -228,7 +228,7 @@ func (f *fleetGateway) execute(ctx context.Context) (*fleetapi.CheckinResponse, // retrieve ack token from the store ackToken := f.stateStore.AckToken() if ackToken != "" { - f.log.Debug("using previously saved ack token: %v", ackToken) + f.log.Debugf("using previously saved ack token: %v", ackToken) } // checkin diff --git a/x-pack/elastic-agent/pkg/agent/application/pipeline/actions/handlers/handler_action_application.go b/x-pack/elastic-agent/pkg/agent/application/pipeline/actions/handlers/handler_action_application.go index 3dec3408735..e87d32ad267 100644 --- a/x-pack/elastic-agent/pkg/agent/application/pipeline/actions/handlers/handler_action_application.go +++ b/x-pack/elastic-agent/pkg/agent/application/pipeline/actions/handlers/handler_action_application.go @@ -7,15 +7,20 @@ package handlers import ( "context" "fmt" + "time" "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/storage/store" "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/core/logger" + "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/core/server" "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/fleetapi" ) +const defaultActionTimeout = time.Minute + // AppAction is a handler for application actions. type AppAction struct { log *logger.Logger + srv *server.Server } // NewAppAction creates a new AppAction handler. @@ -33,9 +38,44 @@ func (h *AppAction) Handle(ctx context.Context, a fleetapi.Action, acker store.F return fmt.Errorf("invalid type, expected ActionApp and received %T", a) } - _ = action + appState, ok := h.srv.FindByInputType(action.InputType) + if !ok { + return fmt.Errorf("matching app is not found for action input: %s", action.InputType) + } + + params, err := action.MarshalMap() + if err != nil { + return err + } + + start := time.Now().UTC() + res, err := appState.PerformAction(action.InputType, params, defaultActionTimeout) + end := time.Now().UTC() - // TODO: handle app action + startFormatted := start.Format(time.RFC3339Nano) + endFormatted := end.Format(time.RFC3339Nano) + if err != nil { + action.StartedAt = startFormatted + action.CompletedAt = endFormatted + action.Error = err.Error() + } else { + action.StartedAt = readMapString(res, "started_at", startFormatted) + action.CompletedAt = readMapString(res, "completed_at", endFormatted) + action.Error = readMapString(res, "error", "") + } + + return acker.Ack(ctx, action) +} - return nil +func readMapString(m map[string]interface{}, key string, def string) string { + if m == nil { + return def + } + + if v, ok := m[key]; ok { + if s, ok := v.(string); ok && s != "" { + return s + } + } + return def } diff --git a/x-pack/elastic-agent/pkg/agent/program/spec.go b/x-pack/elastic-agent/pkg/agent/program/spec.go index f7d81b74a7e..98e539d2007 100644 --- a/x-pack/elastic-agent/pkg/agent/program/spec.go +++ b/x-pack/elastic-agent/pkg/agent/program/spec.go @@ -32,6 +32,7 @@ type Spec struct { Cmd string `yaml:"cmd"` Args []string `yaml:"args"` Artifact string `yaml:"artifact"` + ActionInputTypes []string `yaml:"action_input_types,omitempty"` LogPaths map[string]string `yaml:"log_paths,omitempty"` MetricEndpoints map[string]string `yaml:"metric_endpoints,omitempty"` Rules *transpiler.RuleList `yaml:"rules"` diff --git a/x-pack/elastic-agent/pkg/core/plugin/process/start.go b/x-pack/elastic-agent/pkg/core/plugin/process/start.go index e177a9882df..2a04bd8b656 100644 --- a/x-pack/elastic-agent/pkg/core/plugin/process/start.go +++ b/x-pack/elastic-agent/pkg/core/plugin/process/start.go @@ -62,6 +62,8 @@ func (a *Application) start(ctx context.Context, t app.Taggable, cfg map[string] if err != nil { return err } + // Set input types from the spec + a.srvState.SetInputTypes(a.desc.Spec().ActionInputTypes) } if a.state.Status != state.Stopped { diff --git a/x-pack/elastic-agent/pkg/core/server/server.go b/x-pack/elastic-agent/pkg/core/server/server.go index 97517eb6ce6..5dbdb5cd265 100644 --- a/x-pack/elastic-agent/pkg/core/server/server.go +++ b/x-pack/elastic-agent/pkg/core/server/server.go @@ -85,6 +85,8 @@ type ApplicationState struct { actionsConn bool actionsDone chan bool actionsLock sync.RWMutex + + inputTypes map[string]struct{} } // Handler is the used by the server to inform of status changes. @@ -193,6 +195,24 @@ func (s *Server) Get(app interface{}) (*ApplicationState, bool) { return foundState, foundState != nil } +// FindByInputType application by input type +func (s *Server) FindByInputType(inputType string) (*ApplicationState, bool) { + var foundState *ApplicationState + s.apps.Range(func(_ interface{}, val interface{}) bool { + as := val.(*ApplicationState) + if as.inputTypes == nil { + return true + } + + if _, ok := as.inputTypes[inputType]; ok { + foundState = as + return false + } + return true + }) + return foundState, foundState != nil +} + // Register registers a new application to connect to the server. func (s *Server) Register(app interface{}, config string) (*ApplicationState, error) { if _, ok := s.Get(app); ok { @@ -685,6 +705,16 @@ func (as *ApplicationState) SetStatus(status proto.StateObserved_Status, msg str return nil } +// SetInputTypes sets the allowed action input types for this application +func (as *ApplicationState) SetInputTypes(inputTypes []string) { + as.checkinLock.Lock() + as.inputTypes = make(map[string]struct{}) + for _, inputType := range inputTypes { + as.inputTypes[inputType] = struct{}{} + } + as.checkinLock.Unlock() +} + // updateStatus updates the current observed status from the application, sends the expected state back to the // application if the server expects it to be different then its observed state, and alerts the handler on the // server when the application status has changed. diff --git a/x-pack/elastic-agent/pkg/core/server/server_test.go b/x-pack/elastic-agent/pkg/core/server/server_test.go index d1d1aa5bf2e..b879d53728d 100644 --- a/x-pack/elastic-agent/pkg/core/server/server_test.go +++ b/x-pack/elastic-agent/pkg/core/server/server_test.go @@ -741,7 +741,7 @@ func (*EchoAction) Name() string { return "echo" } -func (*EchoAction) Execute(request map[string]interface{}) (map[string]interface{}, error) { +func (*EchoAction) Execute(ctx context.Context, request map[string]interface{}) (map[string]interface{}, error) { echoRaw, ok := request["echo"] if !ok { return nil, fmt.Errorf("missing required param of echo") @@ -757,7 +757,7 @@ func (*SleepAction) Name() string { return "sleep" } -func (*SleepAction) Execute(request map[string]interface{}) (map[string]interface{}, error) { +func (*SleepAction) Execute(ctx context.Context, request map[string]interface{}) (map[string]interface{}, error) { sleepRaw, ok := request["sleep"] if !ok { return nil, fmt.Errorf("missing required param of slow") @@ -766,7 +766,15 @@ func (*SleepAction) Execute(request map[string]interface{}) (map[string]interfac if !ok { return nil, fmt.Errorf("sleep param must be a number") } - <-time.After(time.Duration(sleep)) + timer := time.NewTimer(time.Duration(sleep)) + defer timer.Stop() + + select { + case <-ctx.Done(): + return nil, ctx.Err() + case <-timer.C: + } + return map[string]interface{}{}, nil } diff --git a/x-pack/elastic-agent/pkg/fleetapi/ack_cmd.go b/x-pack/elastic-agent/pkg/fleetapi/ack_cmd.go index 37177cd7868..8767c4ad9b3 100644 --- a/x-pack/elastic-agent/pkg/fleetapi/ack_cmd.go +++ b/x-pack/elastic-agent/pkg/fleetapi/ack_cmd.go @@ -26,6 +26,11 @@ type AckEvent struct { AgentID string `json:"agent_id"` // : 'agent1', Message string `json:"message,omitempty"` // : 'hello2', Payload string `json:"payload,omitempty"` // : 'payload2', + + ActionData json.RawMessage `json:"action_data,omitempty"` // copy of original action data + StartedAt string `json:"started_at,omitempty"` // time action started + CompletedAt string `json:"completed_at,omitempty"` // time action completed + Error string `json:"error,omitempty"` // optional action error } // AckRequest consists of multiple actions acked to fleet ui. diff --git a/x-pack/elastic-agent/pkg/fleetapi/acker/fleet/fleet_acker.go b/x-pack/elastic-agent/pkg/fleetapi/acker/fleet/fleet_acker.go index 5be52d7dc14..cebf49b027f 100644 --- a/x-pack/elastic-agent/pkg/fleetapi/acker/fleet/fleet_acker.go +++ b/x-pack/elastic-agent/pkg/fleetapi/acker/fleet/fleet_acker.go @@ -99,7 +99,7 @@ func (f *Acker) Commit(ctx context.Context) error { } func constructEvent(action fleetapi.Action, agentID string) fleetapi.AckEvent { - return fleetapi.AckEvent{ + ackev := fleetapi.AckEvent{ EventType: "ACTION_RESULT", SubType: "ACKNOWLEDGED", Timestamp: time.Now().Format(fleetTimeFormat), @@ -107,4 +107,12 @@ func constructEvent(action fleetapi.Action, agentID string) fleetapi.AckEvent { AgentID: agentID, Message: fmt.Sprintf("Action '%s' of type '%s' acknowledged.", action.ID(), action.Type()), } + + if a, ok := action.(*fleetapi.ActionApp); ok { + ackev.ActionData = a.Data + ackev.StartedAt = a.StartedAt + ackev.CompletedAt = a.CompletedAt + ackev.Error = a.Error + } + return ackev } diff --git a/x-pack/elastic-agent/pkg/fleetapi/action.go b/x-pack/elastic-agent/pkg/fleetapi/action.go index 55bf9d1ce26..9aabdd30371 100644 --- a/x-pack/elastic-agent/pkg/fleetapi/action.go +++ b/x-pack/elastic-agent/pkg/fleetapi/action.go @@ -9,6 +9,8 @@ import ( "fmt" "strings" + "github.com/mitchellh/mapstructure" + "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/errors" ) @@ -23,8 +25,8 @@ const ( ActionTypePolicyReassign = "POLICY_REASSIGN" // ActionTypeSettings specifies change of agent settings. ActionTypeSettings = "SETTINGS" - // ActionTypeApplication specifies agent action. - ActionTypeApplication = "APP_ACTION" + // ActionTypeInputAction specifies agent action. + ActionTypeInputAction = "INPUT_ACTION" ) // Action base interface for all the implemented action from the fleet API. @@ -206,10 +208,13 @@ func (a *ActionSettings) String() string { // ActionApp is the application action request. type ActionApp struct { - ActionID string - ActionType string - Application string - Data json.RawMessage + ActionID string `json:"id" mapstructure:"id"` + ActionType string `json:"type" mapstructure:"type"` + InputType string `json:"input_type" mapstructure:"input_type"` + Data json.RawMessage `json:"data" mapstructure:"data"` + StartedAt string `json:"started_at,omitempty" mapstructure:"started_at,omitempty"` + CompletedAt string `json:"completed_at,omitempty" mapstructure:"completed_at,omitempty"` + Error string `json:"error,omitempty" mapstructure:"error,omitempty"` } func (a *ActionApp) String() string { @@ -218,8 +223,8 @@ func (a *ActionApp) String() string { s.WriteString(a.ActionID) s.WriteString(", type: ") s.WriteString(a.ActionType) - s.WriteString(", application: ") - s.WriteString(a.Application) + s.WriteString(", input_type: ") + s.WriteString(a.InputType) return s.String() } @@ -233,19 +238,20 @@ func (a *ActionApp) Type() string { return a.ActionType } +// MarshalMap marshals ActionApp into a corresponding map +func (a *ActionApp) MarshalMap() (map[string]interface{}, error) { + var res map[string]interface{} + err := mapstructure.Decode(a, &res) + return res, err +} + // Actions is a list of Actions to executes and allow to unmarshal heterogenous action type. type Actions []Action // UnmarshalJSON takes every raw representation of an action and try to decode them. func (a *Actions) UnmarshalJSON(data []byte) error { - type r struct { - ActionType string `json:"type"` - Application string `json:"application"` - ActionID string `json:"id"` - Data json.RawMessage `json:"data"` - } - var responses []r + var responses []ActionApp if err := json.Unmarshal(data, &responses); err != nil { return errors.New(err, @@ -273,12 +279,12 @@ func (a *Actions) UnmarshalJSON(data []byte) error { ActionID: response.ActionID, ActionType: response.ActionType, } - case ActionTypeApplication: + case ActionTypeInputAction: action = &ActionApp{ - ActionID: response.ActionID, - ActionType: response.ActionType, - Application: response.Application, - Data: response.Data, + ActionID: response.ActionID, + ActionType: response.ActionType, + InputType: response.InputType, + Data: response.Data, } case ActionTypeUnenroll: action = &ActionUnenroll{ diff --git a/x-pack/elastic-agent/pkg/fleetapi/action_test.go b/x-pack/elastic-agent/pkg/fleetapi/action_test.go new file mode 100644 index 00000000000..28e439699a7 --- /dev/null +++ b/x-pack/elastic-agent/pkg/fleetapi/action_test.go @@ -0,0 +1,82 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package fleetapi + +import ( + "encoding/json" + "testing" + + "github.com/google/go-cmp/cmp" +) + +func TestActionSerialization(t *testing.T) { + a := ActionApp{ + ActionID: "1231232", + ActionType: "APP_INPUT", + InputType: "osquery", + Data: []byte(`{ "foo": "bar" }`), + } + + m, err := a.MarshalMap() + if err != nil { + t.Fatal(err) + } + + diff := cmp.Diff(4, len(m)) + if diff != "" { + t.Error(diff) + } + + diff = cmp.Diff(a.ActionID, mapStringVal(m, "id")) + if diff != "" { + t.Error(diff) + } + + diff = cmp.Diff(a.ActionType, mapStringVal(m, "type")) + if diff != "" { + t.Error(diff) + } + + diff = cmp.Diff(a.InputType, mapStringVal(m, "input_type")) + if diff != "" { + t.Error(diff) + } + + diff = cmp.Diff(a.Data, mapRawMessageVal(m, "data")) + if diff != "" { + t.Error(diff) + } + + diff = cmp.Diff(a.StartedAt, mapStringVal(m, "started_at")) + if diff != "" { + t.Error(diff) + } + diff = cmp.Diff(a.CompletedAt, mapStringVal(m, "completed_at")) + if diff != "" { + t.Error(diff) + } + diff = cmp.Diff(a.Error, mapStringVal(m, "error")) + if diff != "" { + t.Error(diff) + } +} + +func mapStringVal(m map[string]interface{}, key string) string { + if v, ok := m[key]; ok { + if s, ok := v.(string); ok { + return s + } + } + return "" +} + +func mapRawMessageVal(m map[string]interface{}, key string) json.RawMessage { + if v, ok := m[key]; ok { + if res, ok := v.(json.RawMessage); ok { + return res + } + } + return nil +} diff --git a/x-pack/libbeat/management/fleet/manager.go b/x-pack/libbeat/management/fleet/manager.go index 8aec0af1800..8ec84c10579 100644 --- a/x-pack/libbeat/management/fleet/manager.go +++ b/x-pack/libbeat/management/fleet/manager.go @@ -39,6 +39,7 @@ type Manager struct { lock sync.Mutex status management.Status msg string + payload map[string]interface{} stopFunc func() } @@ -173,7 +174,21 @@ func (cm *Manager) OnConfig(s string) { return } - cm.client.Status(proto.StateObserved_HEALTHY, "Running", nil) + cm.client.Status(proto.StateObserved_HEALTHY, "Running", cm.payload) +} + +func (cm *Manager) RegisterAction(action client.Action) { + cm.client.RegisterAction(action) +} + +func (cm *Manager) UnregisterAction(action client.Action) { + cm.client.UnregisterAction(action) +} + +func (cm *Manager) SetPayload(payload map[string]interface{}) { + cm.lock.Lock() + cm.payload = payload + cm.lock.Unlock() } func (cm *Manager) OnStop() { diff --git a/x-pack/libbeat/management/manager.go b/x-pack/libbeat/management/manager.go index e9dbf7511a4..cfbf72f234c 100644 --- a/x-pack/libbeat/management/manager.go +++ b/x-pack/libbeat/management/manager.go @@ -10,6 +10,7 @@ import ( "time" "github.com/elastic/beats/v7/libbeat/common/reload" + "github.com/elastic/elastic-agent-client/v7/pkg/client" "github.com/gofrs/uuid" @@ -115,6 +116,12 @@ func (cm *ConfigManager) Enabled() bool { return cm.config.Enabled } +func (cm *ConfigManager) RegisterAction(action client.Action) {} + +func (cm *ConfigManager) UnregisterAction(action client.Action) {} + +func (cm *ConfigManager) SetPayload(map[string]interface{}) {} + // Start the config manager func (cm *ConfigManager) Start(_ func()) { if !cm.Enabled() {