Skip to content

Commit

Permalink
feat(api): add Details func to EventsService
Browse files Browse the repository at this point in the history
The new `Events.Details(ID)` function returns details about the
specified `event_id`

Signed-off-by: Salim Afiune Maya <[email protected]>
  • Loading branch information
afiune committed Apr 23, 2020
1 parent dbdceef commit 56b95ca
Show file tree
Hide file tree
Showing 3 changed files with 336 additions and 0 deletions.
1 change: 1 addition & 0 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const (
apiVulnerabilitiesReportFromID = "external/vulnerabilities/container/imageId/%s"
apiVulnerabilitiesReportFromDigest = "external/vulnerabilities/container/imageDigest/%s"

apiEventsDetails = "external/events/GetEventDetails"
apiEventsDateRange = "external/events/GetEventsForDateRange"
)

Expand Down
181 changes: 181 additions & 0 deletions api/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,187 @@ func (svc *EventsService) ListRange(start, end time.Time) (
return
}

// Details returns details about the specified event_id
func (svc *EventsService) Details(eventID string) (response EventDetailsResponse, err error) {
if eventID == "" {
err = errors.New("event_id cannot be empty")
return
}

apiPath := fmt.Sprintf("%s?EVENT_ID=%s", apiEventsDetails, eventID)
err = svc.client.RequestDecoder("GET", apiPath, nil, &response)
return
}

type EventDetailsResponse struct {
Events []EventDetails `json:"data"`
}

type EventDetails struct {
EventID string `json:"event_id"`
EventActor string `json:"event_actor"`
EventModel string `json:"event_model"`
EventType string `json:"event_type"`
StartTime time.Time `json:"start_time"`
EndTime time.Time `json:"end_time"`
EntityMap EventEntityMap `json:"entity_map"`
}

type EventEntityMap struct {
User []eventUserEntity `json:"user,omitempty"`
Application []eventApplicationEntity `json:"application,omitempty"`
Machine []eventMachineEntity `json:"machine,omitempty"`
Container []eventContainerEntity `json:"container,omitempty"`
DnsName []eventDnsNameEntity `json:"DnsName,omitempty"` // @afiune not in standard
IpAddress []eventIpAddressEntity `json:"IpAddress,omitempty"` // @afiune not in standard
Process []eventProcessEntity `json:"process,omitempty"`
FileDataHash []eventFileDataHashEntity `json:"FileDataHash,omitempty"` // @afiune not in standard
FileExePath []eventFileExePathEntity `json:"FileExePath,omitempty"` // @afiune not in standard
SourceIpAddress []eventSourceIpAddressEntity `json:"SourceIpAddress,omitempty"` // @afiune not in standard
API []eventAPIEntity `json:"api,omitempty"`
Region []eventRegionEntity `json:"region,omitempty"`
CTUser []eventCTUserEntity `json:"ct_user,omitempty"`
Resource []eventResourceEntity `json:"resource,omitempty"`
RecID []eventRecIDEntity `json:"RecId,omitempty"` // @afiune not in standard
CustomRule []eventCustomRuleEntity `json:"CustomRule,omitempty"` // @afiune not in standard
NewViolation []eventNewViolationEntity `json:"NewViolation,omitempty"` // @afiune not in standard
ViolationReason []eventViolationReasonEntity `json:"ViolationReason,omitempty"` // @afiune not in standard
}
type eventUserEntity struct {
MachineHostname string `json:"machine_hostname"`
Username string `json:"username"`
}

type eventApplicationEntity struct {
Application string `json:"application"`
HasExternalConns int32 `json:"has_external_conns"`
IsClient int32 `json:"is_client"`
IsServer int32 `json:"is_server"`
EarliestKnownTime time.Time `json:"earliest_known_time"`
}

type eventMachineEntity struct {
Hostname string `json:"hostname"`
ExternalIp string `json:"external_ip"`
InstanceID string `json:"instance_id"`
InstanceName string `json:"instance_name"`
CpuPercentage float32 `json:"cpu_percentage"`
InternalIpAddress string `json:"internal_ip_address"`
}

type eventContainerEntity struct {
ImageRepo string `json:"image_repo"`
ImageTag string `json:"image_tag"`
HasExternalConns int32 `json:"has_external_conns"`
IsClient int32 `json:"is_client"`
IsServer int32 `json:"is_server"`
FirstSeenTime time.Time `json:"first_seen_time"`
PodNamespace string `json:"pod_namespace"`
PodIpAddr string `json:"pod_ip_addr"`
}

type eventDnsNameEntity struct {
Hostname string `json:"hostname"`
PortList []int32 `json:"port_list"`
TotalInBytes int32 `json:"total_in_bytes"`
TotalOutBytes int32 `json:"total_out_bytes"`
}

type eventIpAddressEntity struct {
IpAddress string `json:"ip_address"`
TotalInBytes int32 `json:"total_in_bytes"`
TotalOutBytes int32 `json:"total_out_bytes"`
ThreatTags []string `json:"threat_tags"`
ThreatSource map[string]interface{} `json:"threat_source"`
Country string `json:"country"`
Region string `json:"region"`
PortList []int32 `json:"port_list"`
FirstSeenTime time.Time `json:"first_seen_time"`
}

type eventProcessEntity struct {
Hostname string `json:"hostname"`
ProcessId int32 `json:"process_id"`
ProcessStartTime time.Time `json:"process_start_time"`
Cmdline string `json:"cmdline"`
CpuPercentage int32 `json:"cpu_percentage"`
}

type eventFileDataHashEntity struct {
FiledataHash string `json:"filedata_hash"`
MachineCount int32 `json:"machine_count"`
ExePathList []string `json:"exe_path_list"`
FirstSeenTime time.Time `json:"first_seen_time"`
IsKnownBad int32 `json:"is_known_bad"`
}

type eventFileExePathEntity struct {
ExePath string `json:"exe_path"`
FirstSeenTime time.Time `json:"first_seen_time"`
LastFiledataHash string `json:"last_filedata_hash"`
LastPackageName string `json:"last_package_name"`
LastVersion string `json:"last_version"`
LastFileOwner string `json:"last_file_owner"`
}

type eventSourceIpAddressEntity struct {
IpAddress string `json:"ip_address"`
Region string `json:"region"`
Country string `json:"country"`
}

type eventAPIEntity struct {
Service string `json:"service"`
Api string `json:"api"`
}

type eventRegionEntity struct {
Region string `json:"region"`
AccountList []string `json:"account_list"`
}

type eventCTUserEntity struct {
Username string `json:"username"`
AccoutId string `json:"accout_id"`
Mfa int32 `json:"mfa"`
ApiList []string `json:"api_list"`
RegionList []string `json:"region_list"`
PrincipalId string `json:"principal_id"`
}

type eventResourceEntity struct {
Name string `json:"name"`
Value string `json:"value"`
}

type eventRecIDEntity struct {
RecId string `json:"rec_id"`
AccountId string `json:"account_id"`
AccountAlias string `json:"account_alias"`
Title string `json:"title"`
Status string `json:"status"`
EvalType string `json:"eval_type"`
EvalGuid string `json:"eval_guid"`
}

type eventCustomRuleEntity struct {
LastUpdatedTime time.Time `json:"last_updated_time"`
LastUpdatedUser string `json:"last_updated_user"`
DisplayFilter string `json:"display_filter"`
RuleGuid string `json:"rule_guid"`
}

type eventNewViolationEntity struct {
RecId string `json:"rec_id"`
Reason string `json:"reason"`
Resource string `json:"resource"`
}

type eventViolationReasonEntity struct {
RecId string `json:"rec_id"`
Reason string `json:"reason"`
}

type EventsResponse struct {
Events []Event `json:"data"`
}
Expand Down
154 changes: 154 additions & 0 deletions api/events_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,63 @@ func TestEventsList(t *testing.T) {
}
}

func TestEventsDetailsErrorEmptyID(t *testing.T) {
c, err := api.NewClient("test", api.WithToken("TOKEN"))
assert.Nil(t, err)

// a tipical user input error could be that they provide an empty event_id
response, err := c.Events.Details("")
assert.Empty(t, response)
if assert.NotNil(t, err) {
assert.Equal(t,
"event_id cannot be empty",
err.Error(), "error message mismatch",
)
}
}

func TestEventsDetails(t *testing.T) {
fakeServer := lacework.MockServer()
fakeServer.MockAPI(
"external/events/GetEventDetails",
func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "GET", r.Method, "Details should be a GET method")

eventID, ok := r.URL.Query()["EVENT_ID"]
if assert.True(t, ok,
"EVENT_ID parameter missing") {

fmt.Fprintf(w, eventDetailsResponse(eventID[0]))
}
},
)
defer fakeServer.Close()

c, err := api.NewClient("test",
api.WithToken("TOKEN"),
api.WithURL(fakeServer.URL()),
)
assert.Nil(t, err)

response, err := c.Events.Details("123")
assert.Nil(t, err)
if assert.NotNil(t, response) {
if assert.Equal(t, 1, len(response.Events)) {
eventDetails := response.Events[0]
assert.Equal(t, "123", eventDetails.EventID)
assert.Equal(t, "User", eventDetails.EventActor)
assert.Equal(t, "UserTracking", eventDetails.EventModel)
assert.Equal(t, "NewExternalServerDNSConn", eventDetails.EventType)

// TODO @afiune assert EntityMap
assert.Equal(t,
"ruby chef-client",
eventDetails.EntityMap.Application[0].Application,
)
}
}
}

func arrayOfEventsResponse(t string) string {
return `
{
Expand Down Expand Up @@ -158,3 +215,100 @@ func arrayOfEventsResponse(t string) string {
}
`
}

// @afiune real response from a demo environment
func eventDetailsResponse(id string) string {
return `
{
"data": [
{
"END_TIME": "2020-04-20T20:00:00Z",
"ENTITY_MAP": {
"Application": [
{
"APPLICATION": "ruby chef-client",
"EARLIEST_KNOWN_TIME": "2020-04-20T19:00:00Z",
"HAS_EXTERNAL_CONNS": 1,
"IS_CLIENT": 0,
"IS_SERVER": 1
}
],
"FileExePath": [
{ "EXE_PATH": "/opt/chef/embedded/bin/ruby" },
{ "EXE_PATH": "/opt/chef/embedded/bin/ruby" },
{ "EXE_PATH": "/opt/chef/embedded/bin/ruby" }
],
"Machine": [
{
"CPU_PERCENTAGE": 0.39,
"EXTERNAL_IP": "10.0.2.15",
"HOSTNAME": "default-centos-8.vagrantup.com",
"INSTANCE_ID": "",
"INTERNAL_IP_ADDR": "10.0.2.15",
"IS_EXTERNAL": 1
},
{
"CPU_PERCENTAGE": 0.31,
"EXTERNAL_IP": "10.0.2.15",
"HOSTNAME": "default-centos-8.vagrantup.com",
"INSTANCE_ID": "",
"INTERNAL_IP_ADDR": "10.0.2.15",
"IS_EXTERNAL": 1
},
{
"CPU_PERCENTAGE": 0.42,
"EXTERNAL_IP": "10.0.2.15",
"HOSTNAME": "default-centos-8.vagrantup.com",
"INSTANCE_ID": "",
"INTERNAL_IP_ADDR": "10.0.2.15",
"IS_EXTERNAL": 1
}
],
"Process": [
{
"CMDLINE": "chef-client worker: ppid=3803;start=19:47:41;",
"CPU_PERCENTAGE": 0,
"HOSTNAME": "default-centos-8.vagrantup.com",
"PROCESS_ID": 3810,
"PROCESS_START_TIME": "2020-04-20T19:47:40Z"
},
{
"CMDLINE": "chef-client worker: ppid=5328;start=19:54:08;",
"CPU_PERCENTAGE": 0,
"HOSTNAME": "default-centos-8.vagrantup.com",
"PROCESS_ID": 5346,
"PROCESS_START_TIME": "2020-04-20T19:54:08Z"
},
{
"CMDLINE": "chef-client worker: ppid=12057;start=19:47:54;",
"CPU_PERCENTAGE": 0,
"HOSTNAME": "default-centos-8.vagrantup.com",
"PROCESS_ID": 12062,
"PROCESS_START_TIME": "2020-04-20T19:47:54Z"
}
],
"User": [
{
"MACHINE_HOSTNAME": "default-centos-8.vagrantup.com",
"USERNAME": "root"
},
{
"MACHINE_HOSTNAME": "default-centos-8.vagrantup.com",
"USERNAME": "root"
},
{
"MACHINE_HOSTNAME": "default-centos-8.vagrantup.com",
"USERNAME": "root"
}
]
},
"EVENT_ACTOR": "User",
"EVENT_ID": "` + id + `",
"EVENT_MODEL": "UserTracking",
"EVENT_TYPE": "NewExternalServerDNSConn",
"START_TIME": "2020-04-20T19:00:00Z"
}
]
}
`
}

0 comments on commit 56b95ca

Please sign in to comment.