diff --git a/resources/providers/awslib/ec2/ec2_instance.go b/resources/providers/awslib/ec2/ec2_instance.go index 4908361fe6..a41a6d7077 100644 --- a/resources/providers/awslib/ec2/ec2_instance.go +++ b/resources/providers/awslib/ec2/ec2_instance.go @@ -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 "" @@ -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 +} diff --git a/vulnerability/events_creator.go b/vulnerability/events_creator.go index a77b92e9f3..fa3f568547 100644 --- a/vulnerability/events_creator.go +++ b/vulnerability/events_creator.go @@ -19,6 +19,8 @@ package vulnerability import ( "context" + "encoding/json" + "fmt" "strings" "time" @@ -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 { @@ -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"` @@ -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}, @@ -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(), @@ -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{ @@ -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) } @@ -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 +}