Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 128 additions & 0 deletions x-pack/agent/pkg/fleetapi/action.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
// 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"
"fmt"
"strings"

"github.com/pkg/errors"
)

// Action base interface for all the implemented action from the fleet API.
type Action interface {
fmt.Stringer
Type() string
ID() string
}

// ActionBase is the base of all actions to be executed.
type ActionBase struct {
ActionID string
ActionType string
}

// Type returns the action type.
func (a *ActionBase) Type() string {
return a.ActionType
}

// ID returns the action ID.
func (a *ActionBase) ID() string {
return a.ActionID
}

// ActionUnknown is an action that is not know by the current version of the Agent and we don't want
// to return an error at parsing time but at execution time we can report or ignore.
//
// NOTE: We only keep the original type and the action id, the payload of the event is dropped, we
// do this to make sure we do not leak any unwanted information.
type ActionUnknown struct {
*ActionBase
originalType string
}

// Type returns the type of the Action.
func (a *ActionUnknown) Type() string {
return "UNKNOWN"
}

func (a *ActionUnknown) String() string {
var s strings.Builder
s.WriteString("action_id: ")
s.WriteString(a.ID())
s.WriteString(", type: ")
s.WriteString(a.Type())
s.WriteString(" (original type: ")
s.WriteString(a.OriginalType())
s.WriteString(")")
return s.String()
}

// OriginalType returns the original type of the action as returned by the API.
func (a *ActionUnknown) OriginalType() string {
return a.originalType
}

// ActionPolicyChange is a request to apply a new
type ActionPolicyChange struct {
*ActionBase
Policy map[string]interface{} `json:"policy"`
}

func (a *ActionPolicyChange) String() string {
var s strings.Builder
s.WriteString("action_id: ")
s.WriteString(a.ID())
s.WriteString(", type: ")
s.WriteString(a.Type())
return s.String()
}

// 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"`
ActionID string `json:"id"`
Data json.RawMessage `json:"data"`
}

var responses []r

if err := json.Unmarshal(data, &responses); err != nil {
return errors.Wrap(err, "fail to decode actions")
}

actions := make([]Action, 0, len(responses))
var action Action

for _, response := range responses {
switch response.ActionType {
case "POLICY_CHANGE":
action = &ActionPolicyChange{
ActionBase: &ActionBase{
ActionID: response.ActionID,
ActionType: response.ActionType,
},
}
if err := json.Unmarshal(response.Data, action); err != nil {
return errors.Wrap(err, "fail to decode POLICY_CHANGE action")
}
default:
action = &ActionUnknown{
ActionBase: &ActionBase{ActionID: response.ActionID, ActionType: "UNKNOWN"},
originalType: response.ActionType,
}
}
actions = append(actions, action)
}

*a = actions
return nil
}
68 changes: 26 additions & 42 deletions x-pack/agent/pkg/fleetapi/checkin_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,67 +8,47 @@ import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"time"

"github.com/pkg/errors"
)

// CheckinRequest consists of multiple events reported to fleet ui.
//
// Example:
// POST /api/fleet/agents/a4937110-e53e-11e9-934f-47a8e38a522c/checkin
// {
// "events": [{
// "type": "STATE",
// "subtype": "STARTING",
// "message": "state changed from STOPPED to STARTING",
// "timestamp": "2019-10-01T13:42:54.323Z",
// "payload": {},
// "data": "{}"
// }]
// }
type CheckinRequest struct {
Events []Event `json:"events"`
Events []SerializableEvent `json:"events"`
}

// Event is a single event out of collection of reported events.
type Event struct {
EventType string `json:"type"`
Timestamp string `json:"timestamp"`
SubType string `json:"subtype"`
Message string `json:"message"`
Payload map[string]interface{} `json:"payload,omitempty"`
Data string `json:"data,omitempty"`
// SerializableEvent is a representation of the event to be send to the Fleet API via the checkin
// endpoint, we are liberal into what we accept to be send you only need a type and be able to be
// serialized into JSON.
type SerializableEvent interface {
// Type return the type of the event, this must be included in the serialized document.
Type() string

// Timestamp is used to keep track when the event was created in the system.
Timestamp() time.Time

// Message is a human readable string to explain what the event does, this would be displayed in
// the UI as a string of text.
Message() string
}

// Validate validates the enrollment request before sending it to the API.
func (e *CheckinRequest) Validate() error {
if len(e.Events) == 0 {
return errors.New("no events to report")
}

return nil
}

// CheckinResponse is a fleets response to checking API request.
//
// Example:
// {
// "action": "checkin",
// "success": true,
// "policy": {
// },
// "actions": []
// }
// CheckinResponse is the response send back from the server which contains all the action that
// need to be executed or proxy to running processes.
type CheckinResponse struct {
Action string `json:"action"`
Success bool `json:"success"`
Actions Actions `json:"actions"`
Success bool `json:"success"`
}

// Validate validates the response send from the server.
func (e *CheckinResponse) Validate() error {
var err error

return err
return nil
}

// CheckinCmd is a fleet API command.
Expand Down Expand Up @@ -100,10 +80,14 @@ func (e *CheckinCmd) Execute(r *CheckinRequest) (*CheckinResponse, error) {

resp, err := e.client.Send("POST", e.checkinPath, nil, nil, bytes.NewBuffer(b))
if err != nil {
return nil, err
return nil, errors.Wrap(err, "fail to checkin to fleet")
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
return nil, extract(resp.Body)
}

checkinResponse := &CheckinResponse{}
decoder := json.NewDecoder(resp.Body)
if err := decoder.Decode(checkinResponse); err != nil {
Expand Down
Loading