Skip to content
35 changes: 35 additions & 0 deletions resources/providers/awslib/ec2/ec2_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ type Ec2Instance struct {
awsAccount string
}

type SecurityGroupInfo struct {
GroupId *string `json:"group_id,omitempty"`
GroupName *string `json:"group_name,omitempty"`
}

func (i Ec2Instance) GetResourceArn() string {
if i.Instance.InstanceId == nil {
return ""
Expand Down Expand Up @@ -61,3 +66,33 @@ func (i Ec2Instance) GetResourceId() string {
func (i Ec2Instance) GetResourceType() string {
return fetching.EC2Type
}

// TODO: Use genertic implementation with custom functions
func (i Ec2Instance) GetResourceTags() map[string]string {
instanceTags := make(map[string]string, len(i.Tags))
for _, tag := range i.Tags {
instanceTags[*tag.Key] = *tag.Value
}
return instanceTags
}

// TODO: Use genertic implementation with custom functions
func (i Ec2Instance) GetResourceMacAddresses() []string {
macAddresses := make([]string, len(i.NetworkInterfaces))
for i, iface := range i.NetworkInterfaces {
macAddresses[i] = *iface.MacAddress
}
return macAddresses
}

// TODO: Use genertic implementation with custom functions
func (i Ec2Instance) GetResourceSecurityGroups() []SecurityGroupInfo {
securityGroups := make([]SecurityGroupInfo, len(i.SecurityGroups))
for i, group := range i.SecurityGroups {
securityGroups[i] = SecurityGroupInfo{
GroupId: group.GroupId,
GroupName: group.GroupName,
}
}
return securityGroups
}
197 changes: 179 additions & 18 deletions vulnerability/events_creator.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ package vulnerability

import (
"context"
"encoding/json"
"fmt"
"strings"
"time"

Expand All @@ -39,22 +41,79 @@ import (
)

type Vulnerability struct {
Cvss VendorCVSS `json:"cvss,omitempty"`
DataSource *DataSource `json:"data_source,omitempty"`
Scanner Scanner `json:"scanner,omitempty"`
Score Score `json:"score,omitempty"`
Package Package `json:"package,omitempty"`
Cwe []string `json:"cwe,omitempty"`
ID string `json:"id,omitempty"`
Title string `json:"title,omitempty"`
Enumeration string `json:"enumeration,omitempty"`
Reference string `json:"reference,omitempty"`
Description string `json:"description,omitempty"`
Severity string `json:"severity,omitempty"`
Classification string `json:"classification,omitempty"`
PublishedDate *time.Time `json:"published_date,omitempty"`
ReportId int64 `json:"report_id,omitempty"`
Class trivyTypes.ResultClass `json:"class,omitempty"`
Cvss VendorCVSS `json:"cvss,omitempty"`
DataSource *DataSource `json:"data_source,omitempty"`
Scanner Scanner `json:"scanner,omitempty"`
Score Score `json:"score,omitempty"`
Package Package `json:"package,omitempty"`
Cwe []string `json:"cwe,omitempty"`
ID string `json:"id,omitempty"`
Title string `json:"title,omitempty"`
Enumeration string `json:"enumeration,omitempty"`
Reference string `json:"reference,omitempty"`
Description string `json:"description,omitempty"`
Severity string `json:"severity,omitempty"`
Classification string `json:"classification,omitempty"`
PublishedDate *time.Time `json:"published_date,omitempty"`
ReportId int64 `json:"report_id,omitempty"`
// Deprecated field Class renamed to Category
Class trivyTypes.ResultClass `json:"class,omitempty"`
Category trivyTypes.ResultClass `json:"category,omitempty"`
}

// We aren't using the cloud processor here
// because we want to assign information regarding
// the scanned resource and not the scanner
type CloudSection struct {
Service Service `json:"service,omitempty"`
Machine Machine `json:"machine,omitempty"`
Tags map[string]string `json:"tags,omitempty"`
AvailabilityZone *string `json:"availability_zone,omitempty"`
Region string `json:"region,omitempty"`
Instance Instance `json:"instance,omitempty"`
}

type NetworkSection struct {
PrivateIp *string `json:"private_ip,omitempty"`
PublicIp *string `json:"public_ip,omitempty"`
MacAddresses []string `json:"mac_addresses,omitempty"`
}

type SecuritySection struct {
SecurityGroups []ec2.SecurityGroupInfo `json:"security_groups,omitempty"`
}

// We aren't using the cloud processor here
// because we want to assign information regarding
// the scanned resource and not the scanner
type HostSection struct {
Architecture string `json:"architecture,omitempty"`
Os Os `json:"os,omitempty"`
Name string `json:"name,omitempty"`
}

type Os struct {
Platform *string `json:"platform,omitempty"`
}

type Service struct {
Name string `json:"name,omitempty"`
}

type Instance struct {
Id string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
}

type Machine struct {
Type string `json:"type,omitempty"`
Authentication AuthInfo `json:"authentication,omitempty"`
LaunchTime *time.Time `json:"launch_time,omitempty"`
Image *string `json:"image,omitempty"`
}

type AuthInfo struct {
Key *string `json:"key,omitempty"`
}

type CVSS struct {
Expand Down Expand Up @@ -90,6 +149,7 @@ type Score struct {
Version string `json:"version,omitempty"`
}

// Deprecated field Resource transferred to Cloud and Host sections
type Resource struct {
ID string `json:"id"`
Name string `json:"name,omitempty"`
Expand Down Expand Up @@ -150,6 +210,72 @@ func (e EventsCreator) GetChan() chan beat.Event {
func (e EventsCreator) generateEvent(reportResult trivyTypes.Result, vul trivyTypes.DetectedVulnerability, snap ec2.EBSSnapshot, seq time.Time) beat.Event {
timestamp := time.Now().UTC()
sequence := seq.Unix()

cloudSec, err := convertStructToMapStr(CloudSection{
Instance: Instance{
Id: snap.Instance.GetResourceId(),
Name: snap.Instance.GetResourceName(),
},
Service: Service{
// TODO: Support more services
Name: "AWS EC2",
},
Machine: Machine{
Type: string(snap.Instance.InstanceType),
Authentication: AuthInfo{
Key: snap.Instance.KeyName,
},
LaunchTime: snap.Instance.LaunchTime,
Image: snap.Instance.ImageId,
},
AvailabilityZone: getAvailabilityZone(snap.Instance),
Region: snap.Instance.Region,
Tags: snap.Instance.GetResourceTags(),
})

// TODO: Should we fail the event if we can't enrich the cloud section?
if err != nil {
e.log.Errorf("failed to enrich cloud section: %v", err)
}

hostSec, err := convertStructToMapStr(HostSection{
Architecture: string(snap.Instance.Architecture),
Os: Os{
// TODO: Investigate how to get the full os name
// Property "Platform PlatformValues" shows
// the value Windows for Windows instances; otherwise blank
// this only gives us information if the platform is windows or not
// picked "PlatformDetails" as it gives us more information
Platform: snap.Instance.PlatformDetails,
},
Name: snap.Instance.GetResourceName(),
})

// TODO: Should we fail the event if we can't enrich the host section?
if err != nil {
e.log.Errorf("failed to enrich host section: %v", err)
}

networkSec, err := convertStructToMapStr(NetworkSection{
PrivateIp: snap.Instance.PrivateIpAddress,
PublicIp: snap.Instance.PublicIpAddress,
MacAddresses: snap.Instance.GetResourceMacAddresses(),
})

// TODO: Should we fail the event if we can't enrich the network section?
if err != nil {
e.log.Errorf("failed to enrich network section: %v", err)
}

securitySec, err := convertStructToMapStr(SecuritySection{
SecurityGroups: snap.Instance.GetResourceSecurityGroups(),
})

// TODO: Should we fail the event if we can't enrich the security section?
if err != nil {
e.log.Errorf("failed to enrich security section: %v", err)
}

event := beat.Event{
// TODO: Maybe configure or get from somewhere else?
Meta: mapstr.M{libevents.FieldMetaIndex: vulIndex},
Expand All @@ -158,6 +284,7 @@ func (e EventsCreator) generateEvent(reportResult trivyTypes.Result, vul trivyTy
"cloudbeat": version.CloudbeatVersion(),
// TODO: Replace sequence with more generic approach
"event": transformer.BuildECSEvent(sequence, timestamp, []string{vulEcsCategory}),
// Deprecated replaced by cloud and host fields
"resource": Resource{
ID: snap.Instance.GetResourceId(),
Name: snap.Instance.GetResourceName(),
Expand All @@ -172,8 +299,10 @@ func (e EventsCreator) generateEvent(reportResult trivyTypes.Result, vul trivyTy
"vulnerability": Vulnerability{
// TODO: Replace sequence with more generic approach
// TODO: Do we need to add the ReportID duplication if we already have the sequence in event?
ReportId: sequence,
// Deprecated field Class renamed to Category
Class: reportResult.Class,
ReportId: sequence,
Category: reportResult.Class,
Cvss: getCVSS(vul),
DataSource: getDataSource(vul),
Scanner: Scanner{
Expand All @@ -200,10 +329,15 @@ func (e EventsCreator) generateEvent(reportResult trivyTypes.Result, vul trivyTy
Classification: vulScoreSystemClass,
PublishedDate: vul.PublishedDate,
},
// TODO: These sections might be overridden by the enricher of proccessor
"cloud": cloudSec,
"host": hostSec,
"network": networkSec,
"security": securitySec,
},
}

err := e.commonDataProvider.EnrichEvent(&event, fetching.ResourceMetadata{Region: snap.Instance.Region})
err = e.commonDataProvider.EnrichEvent(&event, fetching.ResourceMetadata{Region: snap.Instance.Region})
if err != nil {
e.log.Errorf("failed to enrich event: %v", err)
}
Expand Down Expand Up @@ -323,3 +457,30 @@ func getDataSource(vul trivyTypes.DetectedVulnerability) *DataSource {
URL: vul.DataSource.URL,
}
}

func getAvailabilityZone(ins ec2.Ec2Instance) *string {
if ins.Placement == nil {
return nil
}

return ins.Placement.AvailabilityZone
}

// This is a workaround because of mapstr.M implementation (toMapStr)
// We cannot use structs as values in mapstr.M
// Input is a struct, output is a map[string]interface{}
// TODO: We need to find a better solution for this, it wastes resources
func convertStructToMapStr[S any](input S) (mapstr.M, error) {
out := make(mapstr.M)
// Decode and encode as JSON to normalized the types.
marshaled, err := json.Marshal(input)
if err != nil {
return nil, fmt.Errorf("convertStructToMapStr error marshalling to JSON: %w", err)
}
err = json.Unmarshal(marshaled, &out)
if err != nil {
return nil, fmt.Errorf("convertStructToMapStr error unmarshalling from JSON: %w", err)
}

return out, nil
}