diff --git a/api/models/families.go b/api/models/families.go
index 9adc78bbc..6c398adb6 100644
--- a/api/models/families.go
+++ b/api/models/families.go
@@ -102,3 +102,15 @@ func (c *ExploitsConfig) GetScannersList() []string {
return []string{"exploitdb"}
}
+
+func (c *InfoFinderConfig) IsEnabled() bool {
+ return c != nil && c.Enabled != nil && *c.Enabled
+}
+
+func (c *InfoFinderConfig) GetScannersList() []string {
+ if c != nil && c.Scanners != nil && len(*c.Scanners) != 0 {
+ return *c.Scanners
+ }
+
+ return []string{"sshTopology"}
+}
diff --git a/api/models/models.gen.go b/api/models/models.gen.go
index fb0b09881..8b5f4fcbd 100644
--- a/api/models/models.gen.go
+++ b/api/models/models.gen.go
@@ -31,6 +31,15 @@ const (
GCP CloudProvider = "GCP"
)
+// Defines values for InfoType.
+const (
+ InfoTypeSSHAuthorizedKeyFingerprint InfoType = "SSHAuthorizedKeyFingerprint"
+ InfoTypeSSHDaemonKeyFingerprint InfoType = "SSHDaemonKeyFingerprint"
+ InfoTypeSSHKnownHostFingerprint InfoType = "SSHKnownHostFingerprint"
+ InfoTypeSSHPrivateKeyFingerprint InfoType = "SSHPrivateKeyFingerprint"
+ InfoTypeUNKNOWN InfoType = "UNKNOWN"
+)
+
// Defines values for MisconfigurationSeverity.
const (
MisconfigurationHighSeverity MisconfigurationSeverity = "MisconfigurationHighSeverity"
@@ -55,11 +64,11 @@ const (
// Defines values for RootkitType.
const (
- APPLICATION RootkitType = "APPLICATION"
- FIRMWARE RootkitType = "FIRMWARE"
- KERNEL RootkitType = "KERNEL"
- MEMORY RootkitType = "MEMORY"
- UNKNOWN RootkitType = "UNKNOWN"
+ RootkitTypeAPPLICATION RootkitType = "APPLICATION"
+ RootkitTypeFIRMWARE RootkitType = "FIRMWARE"
+ RootkitTypeKERNEL RootkitType = "KERNEL"
+ RootkitTypeMEMORY RootkitType = "MEMORY"
+ RootkitTypeUNKNOWN RootkitType = "UNKNOWN"
)
// Defines values for ScanState.
@@ -107,6 +116,7 @@ const (
// Defines values for ScanType.
const (
EXPLOIT ScanType = "EXPLOIT"
+ INFOFINDER ScanType = "INFOFINDER"
MALWARE ScanType = "MALWARE"
MISCONFIGURATION ScanType = "MISCONFIGURATION"
ROOTKIT ScanType = "ROOTKIT"
@@ -180,6 +190,7 @@ type AssetScan struct {
Exploits *ExploitScan `json:"exploits,omitempty"`
FindingsProcessed *bool `json:"findingsProcessed,omitempty"`
Id *string `json:"id,omitempty"`
+ InfoFinder *InfoFinderScan `json:"infoFinder,omitempty"`
Malware *MalwareScan `json:"malware,omitempty"`
Misconfigurations *MisconfigurationScan `json:"misconfigurations,omitempty"`
ResourceCleanup *ResourceCleanupState `json:"resourceCleanup,omitempty"`
@@ -237,6 +248,7 @@ type AssetScanRelationship struct {
Exploits *ExploitScan `json:"exploits,omitempty"`
FindingsProcessed *bool `json:"findingsProcessed,omitempty"`
Id string `json:"id"`
+ InfoFinder *InfoFinderScan `json:"infoFinder,omitempty"`
Malware *MalwareScan `json:"malware,omitempty"`
Misconfigurations *MisconfigurationScan `json:"misconfigurations,omitempty"`
ResourceCleanup *ResourceCleanupState `json:"resourceCleanup,omitempty"`
@@ -283,6 +295,7 @@ type AssetScanStats struct {
// General Global statistics for asset scan of all families.
General *AssetScanGeneralStats `json:"general,omitempty"`
+ InfoFinder *[]AssetScanInputScanStats `json:"infoFinder,omitempty"`
Malware *[]AssetScanInputScanStats `json:"malware,omitempty"`
Misconfigurations *[]AssetScanInputScanStats `json:"misconfigurations,omitempty"`
Rootkits *[]AssetScanInputScanStats `json:"rootkits,omitempty"`
@@ -295,6 +308,7 @@ type AssetScanStats struct {
type AssetScanStatus struct {
Exploits *AssetScanState `json:"exploits,omitempty"`
General *AssetScanState `json:"general,omitempty"`
+ InfoFinder *AssetScanState `json:"infoFinder,omitempty"`
Malware *AssetScanState `json:"malware,omitempty"`
Misconfigurations *AssetScanState `json:"misconfigurations,omitempty"`
Rootkits *AssetScanState `json:"rootkits,omitempty"`
@@ -444,6 +458,44 @@ type Findings struct {
Items *[]Finding `json:"items,omitempty"`
}
+// InfoFinderConfig defines model for InfoFinderConfig.
+type InfoFinderConfig struct {
+ Enabled *bool `json:"enabled,omitempty"`
+ Scanners *[]string `json:"scanners,omitempty"`
+}
+
+// InfoFinderFindingInfo defines model for InfoFinderFindingInfo.
+type InfoFinderFindingInfo struct {
+ // Data The data found by the scanner in the specific path for a specific type. See example for SSHKnownHostFingerprint info type
+ Data *string `json:"data,omitempty"`
+ ObjectType string `json:"objectType"`
+
+ // Path File path containing the info
+ Path *string `json:"path,omitempty"`
+ ScannerName *string `json:"scannerName,omitempty"`
+ Type *InfoType `json:"type,omitempty"`
+}
+
+// InfoFinderInfo defines model for InfoFinderInfo.
+type InfoFinderInfo struct {
+ // Data The data found by the scanner in the specific path for a specific type. See example for SSHKnownHostFingerprint info type
+ Data *string `json:"data,omitempty"`
+
+ // Path File path containing the info
+ Path *string `json:"path,omitempty"`
+ ScannerName *string `json:"scannerName,omitempty"`
+ Type *InfoType `json:"type,omitempty"`
+}
+
+// InfoFinderScan defines model for InfoFinderScan.
+type InfoFinderScan struct {
+ Infos *[]InfoFinderInfo `json:"infos"`
+ Scanners *[]string `json:"scanners"`
+}
+
+// InfoType defines model for InfoType.
+type InfoType string
+
// Malware defines model for Malware.
type Malware struct {
MalwareName *string `json:"malwareName,omitempty"`
@@ -712,6 +764,7 @@ type ScanExists struct {
// ScanFamiliesConfig The configuration of the scanner families within a scan config
type ScanFamiliesConfig struct {
Exploits *ExploitsConfig `json:"exploits,omitempty"`
+ InfoFinder *InfoFinderConfig `json:"infoFinder,omitempty"`
Malware *MalwareConfig `json:"malware,omitempty"`
Misconfigurations *MisconfigurationsConfig `json:"misconfigurations,omitempty"`
Rootkits *RootkitsConfig `json:"rootkits,omitempty"`
@@ -723,6 +776,7 @@ type ScanFamiliesConfig struct {
// ScanFindingsSummary A summary of the scan findings.
type ScanFindingsSummary struct {
TotalExploits *int `json:"totalExploits,omitempty"`
+ TotalInfoFinder *int `json:"totalInfoFinder,omitempty"`
TotalMalware *int `json:"totalMalware,omitempty"`
TotalMisconfigurations *int `json:"totalMisconfigurations,omitempty"`
TotalPackages *int `json:"totalPackages,omitempty"`
@@ -785,6 +839,7 @@ type ScanSummary struct {
JobsCompleted *int `json:"jobsCompleted,omitempty"`
JobsLeftToRun *int `json:"jobsLeftToRun,omitempty"`
TotalExploits *int `json:"totalExploits,omitempty"`
+ TotalInfoFinder *int `json:"totalInfoFinder,omitempty"`
TotalMalware *int `json:"totalMalware,omitempty"`
TotalMisconfigurations *int `json:"totalMisconfigurations,omitempty"`
TotalPackages *int `json:"totalPackages,omitempty"`
@@ -1624,6 +1679,34 @@ func (t *Finding_FindingInfo) MergeExploitFindingInfo(v ExploitFindingInfo) erro
return err
}
+// AsInfoFinderFindingInfo returns the union data inside the Finding_FindingInfo as a InfoFinderFindingInfo
+func (t Finding_FindingInfo) AsInfoFinderFindingInfo() (InfoFinderFindingInfo, error) {
+ var body InfoFinderFindingInfo
+ err := json.Unmarshal(t.union, &body)
+ return body, err
+}
+
+// FromInfoFinderFindingInfo overwrites any union data inside the Finding_FindingInfo as the provided InfoFinderFindingInfo
+func (t *Finding_FindingInfo) FromInfoFinderFindingInfo(v InfoFinderFindingInfo) error {
+ v.ObjectType = "InfoFinder"
+ b, err := json.Marshal(v)
+ t.union = b
+ return err
+}
+
+// MergeInfoFinderFindingInfo performs a merge with any union data inside the Finding_FindingInfo, using the provided InfoFinderFindingInfo
+func (t *Finding_FindingInfo) MergeInfoFinderFindingInfo(v InfoFinderFindingInfo) error {
+ v.ObjectType = "InfoFinder"
+ b, err := json.Marshal(v)
+ if err != nil {
+ return err
+ }
+
+ merged, err := runtime.JsonMerge(b, t.union)
+ t.union = merged
+ return err
+}
+
func (t Finding_FindingInfo) Discriminator() (string, error) {
var discriminator struct {
Discriminator string `json:"objectType"`
@@ -1640,6 +1723,8 @@ func (t Finding_FindingInfo) ValueByDiscriminator() (interface{}, error) {
switch discriminator {
case "Exploit":
return t.AsExploitFindingInfo()
+ case "InfoFinder":
+ return t.AsInfoFinderFindingInfo()
case "Malware":
return t.AsMalwareFindingInfo()
case "Misconfiguration":
diff --git a/api/openapi.yaml b/api/openapi.yaml
index c829a65fc..01a48e72e 100644
--- a/api/openapi.yaml
+++ b/api/openapi.yaml
@@ -1169,6 +1169,8 @@ components:
type: integer
totalSecrets:
type: integer
+ totalInfoFinder:
+ type: integer
totalVulnerabilities:
$ref: '#/components/schemas/VulnerabilityScanSummary'
@@ -1215,6 +1217,18 @@ components:
$ref: '#/components/schemas/MisconfigurationsConfig'
exploits:
$ref: '#/components/schemas/ExploitsConfig'
+ infoFinder:
+ $ref: '#/components/schemas/InfoFinderConfig'
+
+ InfoFinderConfig:
+ type: object
+ properties:
+ enabled:
+ type: boolean
+ scanners:
+ type: array
+ items:
+ type: string
VulnerabilitiesConfig:
type: object
@@ -1732,6 +1746,8 @@ components:
$ref: '#/components/schemas/MisconfigurationScan'
exploits:
$ref: '#/components/schemas/ExploitScan'
+ infoFinder:
+ $ref: '#/components/schemas/InfoFinderScan'
findingsProcessed:
type: boolean
resourceCleanup:
@@ -1786,6 +1802,10 @@ components:
type: array
items:
$ref: '#/components/schemas/AssetScanInputScanStats'
+ infoFinder:
+ type: array
+ items:
+ $ref: '#/components/schemas/AssetScanInputScanStats'
AssetScanTemplateReadOnly:
type: object
@@ -1844,6 +1864,9 @@ components:
exploits:
$ref: '#/components/schemas/ExploitScan'
readOnly: true
+ infoFinder:
+ $ref: '#/components/schemas/InfoFinderScan'
+ readOnly: true
findingsProcessed:
type: boolean
readOnly: true
@@ -1922,6 +1945,8 @@ components:
$ref: '#/components/schemas/AssetScanState'
exploits:
$ref: '#/components/schemas/AssetScanState'
+ infoFinder:
+ $ref: '#/components/schemas/AssetScanState'
AssetScanState:
type: object
@@ -2237,6 +2262,44 @@ components:
$ref: '#/components/schemas/Misconfiguration'
nullable: true
+ InfoFinderScan:
+ type: object
+ properties:
+ scanners:
+ type: array
+ items:
+ type: string
+ nullable: true
+ infos:
+ type: array
+ items:
+ $ref: '#/components/schemas/InfoFinderInfo'
+ nullable: true
+
+ InfoFinderInfo:
+ type: object
+ properties:
+ scannerName:
+ type: string
+ type:
+ $ref: '#/components/schemas/InfoType'
+ path:
+ type: string
+ description: "File path containing the info"
+ data:
+ type: string
+ description: "The data found by the scanner in the specific path for a specific type. See example for SSHKnownHostFingerprint info type"
+ example: "2048 SHA256:YQuPOM8ld6FOA9HbKCgkCJWHuGt4aTRD7hstjJpRhxc xxxx (RSA)"
+
+ InfoType:
+ type: string
+ enum:
+ - SSHKnownHostFingerprint
+ - SSHAuthorizedKeyFingerprint
+ - SSHPrivateKeyFingerprint
+ - SSHDaemonKeyFingerprint
+ - UNKNOWN
+
ExploitScan:
type: object
properties:
@@ -2268,6 +2331,7 @@ components:
- MISCONFIGURATION
- ROOTKIT
- EXPLOIT
+ - INFOFINDER
FindingExists:
type: object
@@ -2361,6 +2425,16 @@ components:
type: string
required: [objectType]
+ InfoFinderFindingInfo:
+ type: object
+ allOf:
+ - $ref: '#/components/schemas/InfoFinderInfo'
+ - type: object
+ properties:
+ objectType:
+ type: string
+ required: [ objectType ]
+
Finding:
type: object
properties:
@@ -2396,6 +2470,7 @@ components:
- $ref: '#/components/schemas/MisconfigurationFindingInfo'
- $ref: '#/components/schemas/RootkitFindingInfo'
- $ref: '#/components/schemas/ExploitFindingInfo'
+ - $ref: '#/components/schemas/InfoFinderFindingInfo'
discriminator:
propertyName: objectType
mapping:
@@ -2406,6 +2481,7 @@ components:
Misconfiguration: '#/components/schemas/MisconfigurationFindingInfo'
Rootkit: '#/components/schemas/RootkitFindingInfo'
Exploit: '#/components/schemas/ExploitFindingInfo'
+ InfoFinder: '#/components/schemas/InfoFinderFindingInfo'
responses:
Success:
diff --git a/api/server/server.gen.go b/api/server/server.gen.go
index 0e66958fe..25e59d179 100644
--- a/api/server/server.gen.go
+++ b/api/server/server.gen.go
@@ -1080,106 +1080,111 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL
// Base64 encoded, gzipped, json marshaled Swagger object
var swaggerSpec = []string{
- "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",
- "qXurAMsAAA==",
+ "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",
+ "ZtEAAA==",
}
// GetSwagger returns the content of the embedded swagger specification file
diff --git a/pkg/apiserver/database/demo.go b/pkg/apiserver/database/demo.go
index 9614e259c..43ade860e 100644
--- a/pkg/apiserver/database/demo.go
+++ b/pkg/apiserver/database/demo.go
@@ -140,6 +140,9 @@ func createFindings(ctx context.Context, assetScans []models.AssetScan) []models
if assetScan.Rootkits != nil && assetScan.Rootkits.Rootkits != nil {
ret = append(ret, createRootkitFindings(ctx, findingBase, *assetScan.Rootkits.Rootkits)...)
}
+ if assetScan.InfoFinder != nil && assetScan.InfoFinder.Infos != nil {
+ ret = append(ret, createInfoFinderFindings(ctx, findingBase, *assetScan.InfoFinder.Infos)...)
+ }
}
return ret
@@ -295,6 +298,36 @@ func createMisconfigurationFindings(ctx context.Context, base models.Finding, mi
return ret
}
+// nolint:gocognit,prealloc
+func createInfoFinderFindings(ctx context.Context, base models.Finding, infos []models.InfoFinderInfo) []models.Finding {
+ logger := log.GetLoggerFromContextOrDiscard(ctx)
+
+ var ret []models.Finding
+ for _, info := range infos {
+ val := base
+ convB, err := json.Marshal(info)
+ if err != nil {
+ logger.Errorf("Failed to marshal: %v", err)
+ continue
+ }
+ conv := models.InfoFinderFindingInfo{}
+ err = json.Unmarshal(convB, &conv)
+ if err != nil {
+ logger.Errorf("Failed to unmarshal: %v", err)
+ continue
+ }
+ val.FindingInfo = &models.Finding_FindingInfo{}
+ err = val.FindingInfo.FromInfoFinderFindingInfo(conv)
+ if err != nil {
+ logger.Errorf("Failed to convert FromInfoFinderFindingInfo: %v", err)
+ continue
+ }
+ ret = append(ret, val)
+ }
+
+ return ret
+}
+
// nolint:gocognit,prealloc
func createRootkitFindings(ctx context.Context, base models.Finding, rootkits []models.Rootkit) []models.Finding {
logger := log.GetLoggerFromContextOrDiscard(ctx)
@@ -456,6 +489,9 @@ func createScanConfigs(_ context.Context) []models.ScanConfig {
Exploits: &models.ExploitsConfig{
Enabled: utils.PointerTo(false),
},
+ InfoFinder: &models.InfoFinderConfig{
+ Enabled: utils.PointerTo(false),
+ },
Malware: &models.MalwareConfig{
Enabled: utils.PointerTo(false),
},
@@ -481,6 +517,9 @@ func createScanConfigs(_ context.Context) []models.ScanConfig {
Exploits: &models.ExploitsConfig{
Enabled: utils.PointerTo(true),
},
+ InfoFinder: &models.InfoFinderConfig{
+ Enabled: utils.PointerTo(true),
+ },
Malware: &models.MalwareConfig{
Enabled: utils.PointerTo(true),
},
@@ -546,6 +585,7 @@ func createScans(assets []models.Asset, scanConfigs []models.ScanConfig) []model
JobsCompleted: utils.PointerTo[int](2),
JobsLeftToRun: utils.PointerTo[int](0),
TotalExploits: utils.PointerTo[int](0),
+ TotalInfoFinder: utils.PointerTo[int](0),
TotalMalware: utils.PointerTo[int](0),
TotalMisconfigurations: utils.PointerTo[int](0),
TotalPackages: utils.PointerTo[int](4),
@@ -568,6 +608,7 @@ func createScans(assets []models.Asset, scanConfigs []models.ScanConfig) []model
JobsCompleted: utils.PointerTo[int](1),
JobsLeftToRun: utils.PointerTo[int](1),
TotalExploits: utils.PointerTo[int](2),
+ TotalInfoFinder: utils.PointerTo[int](2),
TotalMalware: utils.PointerTo[int](3),
TotalMisconfigurations: utils.PointerTo[int](3),
TotalPackages: utils.PointerTo[int](0),
@@ -613,7 +654,7 @@ func createScans(assets []models.Asset, scanConfigs []models.ScanConfig) []model
}
}
-// nolint:gocognit,maintidx
+// nolint:gocognit,maintidx,cyclop
func createAssetScans(scans []models.Scan) []models.AssetScan {
timeNow := time.Now()
@@ -878,12 +919,58 @@ func createAssetScans(scans []models.Scan) []models.AssetScan {
result.Summary.TotalVulnerabilities = utils.GetVulnerabilityTotalsPerSeverity(nil)
}
+ // Create InfoFinder if needed
+ if *result.ScanFamiliesConfig.InfoFinder.Enabled {
+ result.Status.InfoFinder = &models.AssetScanState{
+ Errors: nil,
+ LastTransitionTime: &timeNow,
+ State: utils.PointerTo(models.AssetScanStateStateInProgress),
+ }
+ result.Stats.InfoFinder = &[]models.AssetScanInputScanStats{
+ {
+ Path: utils.PointerTo("/mnt"),
+ ScanTime: &models.AssetScanScanTime{
+ EndTime: &timeNow,
+ StartTime: utils.PointerTo(timeNow.Add(-5 * time.Second)),
+ },
+ Size: utils.PointerTo(int64(300)),
+ Type: utils.PointerTo("rootfs"),
+ },
+ }
+ result.InfoFinder = &models.InfoFinderScan{
+ Infos: creatInfoFinderInfos(),
+ }
+ result.Summary.TotalInfoFinder = utils.PointerTo(len(*result.InfoFinder.Infos))
+ } else {
+ result.Status.InfoFinder = &models.AssetScanState{
+ State: utils.PointerTo(models.AssetScanStateStateNotScanned),
+ }
+ result.Summary.TotalInfoFinder = utils.PointerTo(0)
+ }
+
assetScans = append(assetScans, result)
}
}
return assetScans
}
+func creatInfoFinderInfos() *[]models.InfoFinderInfo {
+ return &[]models.InfoFinderInfo{
+ {
+ Data: utils.PointerTo("2048 SHA256:YQuPOM8ld6FOA9HbKCgkCJWHuGt4aTRD7hstjJpRhxc xxxx (RSA)"),
+ Path: utils.PointerTo("/home/ec2-user/.ssh/authorized_keys"),
+ ScannerName: utils.PointerTo("sshTopology"),
+ Type: utils.PointerTo(models.InfoTypeSSHAuthorizedKeyFingerprint),
+ },
+ {
+ Data: utils.PointerTo("256 SHA256:gv6snCwAl5+6fY2g5VkmETWb9Mv0zLRkMz8aQyQWAVc xxxx (ED25519)"),
+ Path: utils.PointerTo("/etc/ssh/ssh_host_ed25519_key"),
+ ScannerName: utils.PointerTo("sshTopology"),
+ Type: utils.PointerTo(models.InfoTypeSSHDaemonKeyFingerprint),
+ },
+ }
+}
+
func createSecretsResult() *[]models.Secret {
return &[]models.Secret{
{
diff --git a/pkg/apiserver/database/gorm/odata.go b/pkg/apiserver/database/gorm/odata.go
index 83004c187..694f5effe 100644
--- a/pkg/apiserver/database/gorm/odata.go
+++ b/pkg/apiserver/database/gorm/odata.go
@@ -117,6 +117,10 @@ var schemaMetas = map[string]odatasql.SchemaMeta{
FieldType: odatasql.ComplexFieldType,
ComplexFieldSchemas: []string{"ExploitScan"},
},
+ "infoFinder": odatasql.FieldMeta{
+ FieldType: odatasql.ComplexFieldType,
+ ComplexFieldSchemas: []string{"InfoFinderScan"},
+ },
"stats": odatasql.FieldMeta{
FieldType: odatasql.ComplexFieldType,
ComplexFieldSchemas: []string{"AssetScanStats"},
@@ -163,6 +167,10 @@ var schemaMetas = map[string]odatasql.SchemaMeta{
FieldType: odatasql.ComplexFieldType,
ComplexFieldSchemas: []string{"AssetScanInputScanStats"},
},
+ "infoFinder": odatasql.FieldMeta{
+ FieldType: odatasql.ComplexFieldType,
+ ComplexFieldSchemas: []string{"AssetScanInputScanStats"},
+ },
},
},
"AssetScanGeneralStats": {
@@ -348,6 +356,29 @@ var schemaMetas = map[string]odatasql.SchemaMeta{
"path": odatasql.FieldMeta{FieldType: odatasql.PrimitiveFieldType},
},
},
+ "InfoFinderScan": {
+ Fields: odatasql.Schema{
+ "scanners": odatasql.FieldMeta{
+ FieldType: odatasql.CollectionFieldType,
+ CollectionItemMeta: &odatasql.FieldMeta{FieldType: odatasql.PrimitiveFieldType},
+ },
+ "infos": odatasql.FieldMeta{
+ FieldType: odatasql.CollectionFieldType,
+ CollectionItemMeta: &odatasql.FieldMeta{
+ FieldType: odatasql.ComplexFieldType,
+ ComplexFieldSchemas: []string{"InfoFinderInfo"},
+ },
+ },
+ },
+ },
+ "InfoFinderInfo": {
+ Fields: odatasql.Schema{
+ "scannerName": odatasql.FieldMeta{FieldType: odatasql.PrimitiveFieldType},
+ "type": odatasql.FieldMeta{FieldType: odatasql.PrimitiveFieldType},
+ "path": odatasql.FieldMeta{FieldType: odatasql.PrimitiveFieldType},
+ "data": odatasql.FieldMeta{FieldType: odatasql.PrimitiveFieldType},
+ },
+ },
"ExploitScan": {
Fields: odatasql.Schema{
"exploits": odatasql.FieldMeta{
@@ -431,6 +462,7 @@ var schemaMetas = map[string]odatasql.SchemaMeta{
"totalMisconfigurations": odatasql.FieldMeta{FieldType: odatasql.PrimitiveFieldType},
"totalRootkits": odatasql.FieldMeta{FieldType: odatasql.PrimitiveFieldType},
"totalSecrets": odatasql.FieldMeta{FieldType: odatasql.PrimitiveFieldType},
+ "totalInfoFinder": odatasql.FieldMeta{FieldType: odatasql.PrimitiveFieldType},
"totalVulnerabilities": odatasql.FieldMeta{
FieldType: odatasql.ComplexFieldType,
ComplexFieldSchemas: []string{"VulnerabilityScanSummary"},
@@ -539,6 +571,7 @@ var schemaMetas = map[string]odatasql.SchemaMeta{
"totalMisconfigurations": odatasql.FieldMeta{FieldType: odatasql.PrimitiveFieldType},
"totalRootkits": odatasql.FieldMeta{FieldType: odatasql.PrimitiveFieldType},
"totalSecrets": odatasql.FieldMeta{FieldType: odatasql.PrimitiveFieldType},
+ "totalInfoFinder": odatasql.FieldMeta{FieldType: odatasql.PrimitiveFieldType},
"totalVulnerabilities": odatasql.FieldMeta{
FieldType: odatasql.ComplexFieldType,
ComplexFieldSchemas: []string{"VulnerabilityScanSummary"},
@@ -614,6 +647,21 @@ var schemaMetas = map[string]odatasql.SchemaMeta{
FieldType: odatasql.ComplexFieldType,
ComplexFieldSchemas: []string{"VulnerabilitiesConfig"},
},
+ "infoFinder": odatasql.FieldMeta{
+ FieldType: odatasql.ComplexFieldType,
+ ComplexFieldSchemas: []string{"InfoFinderConfig"},
+ },
+ },
+ },
+ "InfoFinderConfig": {
+ Fields: odatasql.Schema{
+ "enabled": odatasql.FieldMeta{FieldType: odatasql.PrimitiveFieldType},
+ "scanners": odatasql.FieldMeta{
+ FieldType: odatasql.CollectionFieldType,
+ CollectionItemMeta: &odatasql.FieldMeta{
+ FieldType: odatasql.PrimitiveFieldType,
+ },
+ },
},
},
"ExploitsConfig": {
@@ -725,6 +773,7 @@ var schemaMetas = map[string]odatasql.SchemaMeta{
"MisconfigurationFindingInfo",
"RootkitFindingInfo",
"ExploitFindingInfo",
+ "InfoFinderFindingInfo",
},
DiscriminatorProperty: "objectType",
DiscriminatorSchemaMapping: map[string]string{
@@ -735,6 +784,7 @@ var schemaMetas = map[string]odatasql.SchemaMeta{
"MisconfigurationFindingInfo": "Misconfiguration",
"RootkitFindingInfo": "Rootkit",
"ExploitFindingInfo": "Exploit",
+ "InfoFinderFindingInfo": "InfoFinder",
},
},
},
@@ -844,6 +894,15 @@ var schemaMetas = map[string]odatasql.SchemaMeta{
"remediation": odatasql.FieldMeta{FieldType: odatasql.PrimitiveFieldType},
},
},
+ "InfoFinderFindingInfo": {
+ Fields: odatasql.Schema{
+ "objectType": odatasql.FieldMeta{FieldType: odatasql.PrimitiveFieldType},
+ "scannerName": odatasql.FieldMeta{FieldType: odatasql.PrimitiveFieldType},
+ "type": odatasql.FieldMeta{FieldType: odatasql.PrimitiveFieldType},
+ "path": odatasql.FieldMeta{FieldType: odatasql.PrimitiveFieldType},
+ "data": odatasql.FieldMeta{FieldType: odatasql.PrimitiveFieldType},
+ },
+ },
"RootkitFindingInfo": {
Fields: odatasql.Schema{
"objectType": odatasql.FieldMeta{FieldType: odatasql.PrimitiveFieldType},
@@ -900,6 +959,10 @@ var schemaMetas = map[string]odatasql.SchemaMeta{
FieldType: odatasql.ComplexFieldType,
ComplexFieldSchemas: []string{"AssetScanState"},
},
+ "infoFinder": odatasql.FieldMeta{
+ FieldType: odatasql.ComplexFieldType,
+ ComplexFieldSchemas: []string{"AssetScanState"},
+ },
},
},
"AssetScanState": {
diff --git a/pkg/cli/presenter/default.go b/pkg/cli/presenter/default.go
index a7cb537db..9daea0353 100644
--- a/pkg/cli/presenter/default.go
+++ b/pkg/cli/presenter/default.go
@@ -172,6 +172,10 @@ func (p *DefaultPresenter) ExportRootkitResult(_ context.Context, res families.F
}
func (p *DefaultPresenter) ExportInfoFinderResult(_ context.Context, res families.FamilyResult) error {
+ if res.Result == nil {
+ return nil
+ }
+
infoFinderResults, ok := res.Result.(*infofinder.Results)
if !ok {
return fmt.Errorf("failed to convert to infofinder results")
diff --git a/pkg/cli/presenter/vmclarity.go b/pkg/cli/presenter/vmclarity.go
index 6b58c2167..5969a2d4e 100644
--- a/pkg/cli/presenter/vmclarity.go
+++ b/pkg/cli/presenter/vmclarity.go
@@ -25,6 +25,7 @@ import (
"github.com/openclarity/vmclarity/pkg/shared/backendclient"
"github.com/openclarity/vmclarity/pkg/shared/families"
"github.com/openclarity/vmclarity/pkg/shared/families/exploits"
+ "github.com/openclarity/vmclarity/pkg/shared/families/infofinder"
"github.com/openclarity/vmclarity/pkg/shared/families/malware"
"github.com/openclarity/vmclarity/pkg/shared/families/misconfiguration"
"github.com/openclarity/vmclarity/pkg/shared/families/rootkits"
@@ -62,7 +63,7 @@ func (v *VMClarityPresenter) ExportFamilyResult(ctx context.Context, res familie
case types.Malware:
err = v.ExportMalwareResult(ctx, res)
case types.InfoFinder:
- err = fmt.Errorf("InfoFinder family is unsupported")
+ err = v.ExportInfoFinderResult(ctx, res)
}
return err
@@ -379,6 +380,57 @@ func (v *VMClarityPresenter) ExportMisconfigurationResult(ctx context.Context, r
return nil
}
+func (v *VMClarityPresenter) ExportInfoFinderResult(ctx context.Context, res families.FamilyResult) error {
+ assetScan, err := v.client.GetAssetScan(ctx, v.assetScanID, models.GetAssetScansAssetScanIDParams{})
+ if err != nil {
+ return fmt.Errorf("failed to get asset scan: %w", err)
+ }
+
+ if assetScan.Status == nil {
+ assetScan.Status = &models.AssetScanStatus{}
+ }
+ if assetScan.Status.InfoFinder == nil {
+ assetScan.Status.InfoFinder = &models.AssetScanState{}
+ }
+ if assetScan.Summary == nil {
+ assetScan.Summary = &models.ScanFindingsSummary{}
+ }
+ if assetScan.Stats == nil {
+ assetScan.Stats = &models.AssetScanStats{}
+ }
+
+ var errs []string
+
+ if res.Err != nil {
+ errs = append(errs, res.Err.Error())
+ } else {
+ results, ok := res.Result.(*infofinder.Results)
+ if !ok {
+ errs = append(errs, fmt.Errorf("failed to convert to info finder results").Error())
+ } else {
+ apiInfoFinder, err := cliutils.ConvertInfoFinderResultToAPIModel(results)
+ if err != nil {
+ errs = append(errs, fmt.Sprintf("failed to convert info finder results from scan to API model: %v", err))
+ } else {
+ assetScan.InfoFinder = apiInfoFinder
+ assetScan.Summary.TotalInfoFinder = utils.PointerTo(len(results.Infos))
+ }
+ assetScan.Stats.InfoFinder = getInputScanStats(results.Metadata.InputScans)
+ }
+ }
+
+ state := models.AssetScanStateStateDone
+ assetScan.Status.InfoFinder.State = &state
+ assetScan.Status.InfoFinder.Errors = &errs
+
+ err = v.client.PatchAssetScan(ctx, assetScan, v.assetScanID)
+ if err != nil {
+ return fmt.Errorf("failed to patch asset scan: %w", err)
+ }
+
+ return nil
+}
+
func (v *VMClarityPresenter) ExportRootkitResult(ctx context.Context, res families.FamilyResult) error {
assetScan, err := v.client.GetAssetScan(ctx, v.assetScanID, models.GetAssetScansAssetScanIDParams{})
if err != nil {
diff --git a/pkg/cli/state/vmclarity.go b/pkg/cli/state/vmclarity.go
index 2b4b811fa..b259348de 100644
--- a/pkg/cli/state/vmclarity.go
+++ b/pkg/cli/state/vmclarity.go
@@ -173,7 +173,7 @@ func (v *VMClarityState) MarkFamilyScanInProgress(ctx context.Context, familyTyp
case types.Malware:
err = v.markMalwareScanInProgress(ctx)
case types.InfoFinder:
- err = fmt.Errorf("InfoFinder family is unsupported")
+ err = v.markInfoFinderScanInProgress(ctx)
}
return err
}
@@ -278,6 +278,31 @@ func (v *VMClarityState) markVulnerabilitiesScanInProgress(ctx context.Context)
return nil
}
+func (v *VMClarityState) markInfoFinderScanInProgress(ctx context.Context) error {
+ assetScan, err := v.client.GetAssetScan(ctx, v.assetScanID, models.GetAssetScansAssetScanIDParams{})
+ if err != nil {
+ return fmt.Errorf("failed to get asset scan: %w", err)
+ }
+
+ if assetScan.Status == nil {
+ assetScan.Status = &models.AssetScanStatus{}
+ }
+ if assetScan.Status.InfoFinder == nil {
+ assetScan.Status.InfoFinder = &models.AssetScanState{}
+ }
+
+ state := models.AssetScanStateStateInProgress
+ assetScan.Status.InfoFinder.State = &state
+ assetScan.Status.InfoFinder.LastTransitionTime = utils.PointerTo(time.Now())
+
+ err = v.client.PatchAssetScan(ctx, assetScan, v.assetScanID)
+ if err != nil {
+ return fmt.Errorf("failed to patch asset scan: %w", err)
+ }
+
+ return nil
+}
+
func (v *VMClarityState) markMalwareScanInProgress(ctx context.Context) error {
assetScan, err := v.client.GetAssetScan(ctx, v.assetScanID, models.GetAssetScansAssetScanIDParams{})
if err != nil {
diff --git a/pkg/cli/utils/apimodel.go b/pkg/cli/utils/apimodel.go
index f87ef9a1b..1080ffdfc 100644
--- a/pkg/cli/utils/apimodel.go
+++ b/pkg/cli/utils/apimodel.go
@@ -27,6 +27,8 @@ import (
"github.com/openclarity/vmclarity/api/models"
"github.com/openclarity/vmclarity/pkg/shared/families/exploits"
+ "github.com/openclarity/vmclarity/pkg/shared/families/infofinder"
+ "github.com/openclarity/vmclarity/pkg/shared/families/infofinder/types"
"github.com/openclarity/vmclarity/pkg/shared/families/malware"
"github.com/openclarity/vmclarity/pkg/shared/families/misconfiguration"
misconfigurationTypes "github.com/openclarity/vmclarity/pkg/shared/families/misconfiguration/types"
@@ -316,6 +318,46 @@ func ConvertMisconfigurationResultToAPIModel(misconfigurationResults *misconfigu
}, nil
}
+func ConvertInfoFinderResultToAPIModel(results *infofinder.Results) (*models.InfoFinderScan, error) {
+ if results == nil || results.Infos == nil {
+ return &models.InfoFinderScan{}, nil
+ }
+
+ ret := make([]models.InfoFinderInfo, len(results.Infos))
+
+ for i := range results.Infos {
+ info := results.Infos[i]
+
+ ret[i] = models.InfoFinderInfo{
+ Data: &info.Data,
+ Path: &info.Path,
+ ScannerName: &info.ScannerName,
+ Type: convertInfoTypeToAPIModel(info.Type),
+ }
+ }
+
+ return &models.InfoFinderScan{
+ Infos: &ret,
+ Scanners: utils.PointerTo(results.Metadata.Scanners),
+ }, nil
+}
+
+func convertInfoTypeToAPIModel(infoType types.InfoType) *models.InfoType {
+ switch infoType {
+ case types.SSHKnownHostFingerprint:
+ return utils.PointerTo(models.InfoTypeSSHKnownHostFingerprint)
+ case types.SSHAuthorizedKeyFingerprint:
+ return utils.PointerTo(models.InfoTypeSSHAuthorizedKeyFingerprint)
+ case types.SSHPrivateKeyFingerprint:
+ return utils.PointerTo(models.InfoTypeSSHPrivateKeyFingerprint)
+ case types.SSHDaemonKeyFingerprint:
+ return utils.PointerTo(models.InfoTypeSSHDaemonKeyFingerprint)
+ default:
+ log.Errorf("Can't convert info type %q, treating as %v", infoType, models.InfoTypeUNKNOWN)
+ return utils.PointerTo(models.InfoTypeUNKNOWN)
+ }
+}
+
func ConvertRootkitsResultToAPIModel(rootkitsResults *rootkits.Results) *models.RootkitScan {
if rootkitsResults == nil || rootkitsResults.MergedResults == nil {
return &models.RootkitScan{}
@@ -339,17 +381,17 @@ func ConvertRootkitsResultToAPIModel(rootkitsResults *rootkits.Results) *models.
func ConvertRootkitTypeToAPIModel(rootkitType rootkitsTypes.RootkitType) *models.RootkitType {
switch rootkitType {
case rootkitsTypes.APPLICATION:
- return utils.PointerTo(models.APPLICATION)
+ return utils.PointerTo(models.RootkitTypeAPPLICATION)
case rootkitsTypes.FIRMWARE:
- return utils.PointerTo(models.FIRMWARE)
+ return utils.PointerTo(models.RootkitTypeFIRMWARE)
case rootkitsTypes.KERNEL:
- return utils.PointerTo(models.KERNEL)
+ return utils.PointerTo(models.RootkitTypeKERNEL)
case rootkitsTypes.MEMORY:
- return utils.PointerTo(models.MEMORY)
+ return utils.PointerTo(models.RootkitTypeMEMORY)
case rootkitsTypes.UNKNOWN:
- return utils.PointerTo(models.UNKNOWN)
+ return utils.PointerTo(models.RootkitTypeUNKNOWN)
default:
- log.Errorf("Can't convert rootkit type %q, treating as %v", rootkitType, models.UNKNOWN)
- return utils.PointerTo(models.UNKNOWN)
+ log.Errorf("Can't convert rootkit type %q, treating as %v", rootkitType, models.RootkitTypeUNKNOWN)
+ return utils.PointerTo(models.RootkitTypeUNKNOWN)
}
}
diff --git a/pkg/cli/utils/apimodel_test.go b/pkg/cli/utils/apimodel_test.go
index 4a8d92bdd..8e8325a3f 100644
--- a/pkg/cli/utils/apimodel_test.go
+++ b/pkg/cli/utils/apimodel_test.go
@@ -29,6 +29,8 @@ import (
"github.com/openclarity/vmclarity/api/models"
"github.com/openclarity/vmclarity/pkg/shared/families/exploits"
common2 "github.com/openclarity/vmclarity/pkg/shared/families/exploits/common"
+ "github.com/openclarity/vmclarity/pkg/shared/families/infofinder"
+ infofinderTypes "github.com/openclarity/vmclarity/pkg/shared/families/infofinder/types"
"github.com/openclarity/vmclarity/pkg/shared/families/malware"
malwarecommon "github.com/openclarity/vmclarity/pkg/shared/families/malware/common"
"github.com/openclarity/vmclarity/pkg/shared/families/misconfiguration"
@@ -1026,3 +1028,160 @@ func Test_ConvertVulnSeverityToAPIModel(t *testing.T) {
})
}
}
+
+func TestConvertInfoFinderResultToAPIModel(t *testing.T) {
+ type args struct {
+ results *infofinder.Results
+ }
+ tests := []struct {
+ name string
+ args args
+ want *models.InfoFinderScan
+ wantErr bool
+ }{
+ {
+ name: "nil results",
+ args: args{
+ results: nil,
+ },
+ want: &models.InfoFinderScan{},
+ wantErr: false,
+ },
+ {
+ name: "nil results.Info",
+ args: args{
+ results: &infofinder.Results{
+ Infos: nil,
+ },
+ },
+ want: &models.InfoFinderScan{},
+ wantErr: false,
+ },
+ {
+ name: "sanity",
+ args: args{
+ results: &infofinder.Results{
+ Metadata: types.Metadata{
+ Scanners: []string{"scanner1", "scanner2"},
+ },
+ Infos: []infofinder.FlattenedInfos{
+ {
+ ScannerName: "scanner1",
+ Info: infofinderTypes.Info{
+ Type: infofinderTypes.SSHKnownHostFingerprint,
+ Path: "Path1",
+ Data: "Data1",
+ },
+ },
+ {
+ ScannerName: "scanner2",
+ Info: infofinderTypes.Info{
+ Type: infofinderTypes.SSHDaemonKeyFingerprint,
+ Path: "Path2",
+ Data: "Data2",
+ },
+ },
+ {
+ ScannerName: "scanner2",
+ Info: infofinderTypes.Info{
+ Type: infofinderTypes.SSHAuthorizedKeyFingerprint,
+ Path: "Path3",
+ Data: "Data3",
+ },
+ },
+ },
+ },
+ },
+ want: &models.InfoFinderScan{
+ Infos: utils.PointerTo([]models.InfoFinderInfo{
+ {
+ Type: utils.PointerTo(models.InfoTypeSSHKnownHostFingerprint),
+ Path: utils.PointerTo("Path1"),
+ Data: utils.PointerTo("Data1"),
+ ScannerName: utils.PointerTo("scanner1"),
+ },
+ {
+ Type: utils.PointerTo(models.InfoTypeSSHDaemonKeyFingerprint),
+ Path: utils.PointerTo("Path2"),
+ Data: utils.PointerTo("Data2"),
+ ScannerName: utils.PointerTo("scanner2"),
+ },
+ {
+ Type: utils.PointerTo(models.InfoTypeSSHAuthorizedKeyFingerprint),
+ Path: utils.PointerTo("Path3"),
+ Data: utils.PointerTo("Data3"),
+ ScannerName: utils.PointerTo("scanner2"),
+ },
+ }),
+ Scanners: utils.PointerTo([]string{"scanner1", "scanner2"}),
+ },
+ wantErr: false,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got, err := ConvertInfoFinderResultToAPIModel(tt.args.results)
+ if (err != nil) != tt.wantErr {
+ t.Errorf("ConvertInfoFinderResultToAPIModel() error = %v, wantErr %v", err, tt.wantErr)
+ return
+ }
+ if !reflect.DeepEqual(got, tt.want) {
+ t.Errorf("ConvertInfoFinderResultToAPIModel() got = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
+
+func Test_convertInfoTypeToAPIModel(t *testing.T) {
+ type args struct {
+ infoType infofinderTypes.InfoType
+ }
+ tests := []struct {
+ name string
+ args args
+ want *models.InfoType
+ }{
+ {
+ name: "SSHKnownHostFingerprint",
+ args: args{
+ infoType: infofinderTypes.SSHKnownHostFingerprint,
+ },
+ want: utils.PointerTo(models.InfoTypeSSHKnownHostFingerprint),
+ },
+ {
+ name: "SSHAuthorizedKeyFingerprint",
+ args: args{
+ infoType: infofinderTypes.SSHAuthorizedKeyFingerprint,
+ },
+ want: utils.PointerTo(models.InfoTypeSSHAuthorizedKeyFingerprint),
+ },
+ {
+ name: "SSHPrivateKeyFingerprint",
+ args: args{
+ infoType: infofinderTypes.SSHPrivateKeyFingerprint,
+ },
+ want: utils.PointerTo(models.InfoTypeSSHPrivateKeyFingerprint),
+ },
+ {
+ name: "SSHDaemonKeyFingerprint",
+ args: args{
+ infoType: infofinderTypes.SSHDaemonKeyFingerprint,
+ },
+ want: utils.PointerTo(models.InfoTypeSSHDaemonKeyFingerprint),
+ },
+ {
+ name: "unknown",
+ args: args{
+ infoType: "unknown",
+ },
+ want: utils.PointerTo(models.InfoTypeUNKNOWN),
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if got := convertInfoTypeToAPIModel(tt.args.infoType); !reflect.DeepEqual(got, tt.want) {
+ t.Errorf("convertInfoTypeToAPIModel() = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
diff --git a/pkg/orchestrator/assetscanprocessor/infofinder.go b/pkg/orchestrator/assetscanprocessor/infofinder.go
new file mode 100644
index 000000000..6b84fd151
--- /dev/null
+++ b/pkg/orchestrator/assetscanprocessor/infofinder.go
@@ -0,0 +1,156 @@
+// Copyright © 2023 Cisco Systems, Inc. and its affiliates.
+// All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package assetscanprocessor
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/openclarity/vmclarity/api/models"
+ "github.com/openclarity/vmclarity/pkg/shared/findingkey"
+ logutils "github.com/openclarity/vmclarity/pkg/shared/log"
+ "github.com/openclarity/vmclarity/pkg/shared/utils"
+)
+
+func (asp *AssetScanProcessor) getExistingInfoFinderFindingsForScan(ctx context.Context, assetScan models.AssetScan) (map[findingkey.InfoFinderKey]string, error) {
+ logger := logutils.GetLoggerFromContextOrDiscard(ctx)
+
+ existingMap := map[findingkey.InfoFinderKey]string{}
+
+ existingFilter := fmt.Sprintf("findingInfo/objectType eq 'InfoFinder' and foundBy/id eq '%s'", *assetScan.Id)
+ existingFindings, err := asp.client.GetFindings(ctx, models.GetFindingsParams{
+ Filter: &existingFilter,
+ Select: utils.PointerTo("id,findingInfo/scannerName,findingInfo/type,findingInfo/data,findingInfo/path"),
+ })
+ if err != nil {
+ return existingMap, fmt.Errorf("failed to query for findings: %w", err)
+ }
+
+ for _, finding := range *existingFindings.Items {
+ info, err := (*finding.FindingInfo).AsInfoFinderFindingInfo()
+ if err != nil {
+ return existingMap, fmt.Errorf("unable to get InfoFinder finding info: %w", err)
+ }
+
+ key := findingkey.GenerateInfoFinderKey(info)
+ if _, ok := existingMap[key]; ok {
+ return existingMap, fmt.Errorf("found multiple matching existing findings for InfoFinder %v", key)
+ }
+ existingMap[key] = *finding.Id
+ }
+
+ logger.Infof("Found %d existing InfoFinder findings for this scan", len(existingMap))
+ logger.Debugf("Existing InfoFinder map: %v", existingMap)
+
+ return existingMap, nil
+}
+
+// nolint:cyclop
+func (asp *AssetScanProcessor) reconcileResultInfoFindersToFindings(ctx context.Context, assetScan models.AssetScan) error {
+ completedTime := assetScan.Status.General.LastTransitionTime
+
+ newerFound, newerTime, err := asp.newerExistingFindingTime(ctx, assetScan.Asset.Id, "InfoFinder", *completedTime)
+ if err != nil {
+ return fmt.Errorf("failed to check for newer existing InfoFinder findings: %v", err)
+ }
+
+ // Build a map of existing findings for this scan to prevent us
+ // recreating existing ones as we might be re-reconciling the same
+ // asset scan because of downtime or a previous failure.
+ existingMap, err := asp.getExistingInfoFinderFindingsForScan(ctx, assetScan)
+ if err != nil {
+ return fmt.Errorf("failed to check existing InfoFinder findings: %w", err)
+ }
+
+ if assetScan.InfoFinder != nil && assetScan.InfoFinder.Infos != nil {
+ // Create new or update existing findings all the infos found by the
+ // scan.
+ for _, item := range *assetScan.InfoFinder.Infos {
+ itemFindingInfo := models.InfoFinderFindingInfo{
+ Data: item.Data,
+ Path: item.Path,
+ ScannerName: item.ScannerName,
+ Type: item.Type,
+ }
+
+ findingInfo := models.Finding_FindingInfo{}
+ err = findingInfo.FromInfoFinderFindingInfo(itemFindingInfo)
+ if err != nil {
+ return fmt.Errorf("unable to convert InfoFinderFindingInfo into FindingInfo: %w", err)
+ }
+
+ finding := models.Finding{
+ Asset: &models.AssetRelationship{
+ Id: assetScan.Asset.Id,
+ },
+ FoundBy: &models.AssetScanRelationship{
+ Id: *assetScan.Id,
+ },
+ FoundOn: assetScan.Status.General.LastTransitionTime,
+ FindingInfo: &findingInfo,
+ }
+
+ // Set InvalidatedOn time to the FoundOn time of the oldest
+ // finding, found after this asset scan.
+ if newerFound {
+ finding.InvalidatedOn = &newerTime
+ }
+
+ key := findingkey.GenerateInfoFinderKey(itemFindingInfo)
+ if id, ok := existingMap[key]; ok {
+ err = asp.client.PatchFinding(ctx, id, finding)
+ if err != nil {
+ return fmt.Errorf("failed to create finding: %w", err)
+ }
+ } else {
+ _, err = asp.client.PostFinding(ctx, finding)
+ if err != nil {
+ return fmt.Errorf("failed to create finding: %w", err)
+ }
+ }
+ }
+ }
+
+ // Invalidate any findings of this type for this asset where foundOn is
+ // older than this asset scan, and has not already been invalidated by
+ // an asset scan older than this asset scan.
+ err = asp.invalidateOlderFindingsByType(ctx, "InfoFinder", assetScan.Asset.Id, *completedTime)
+ if err != nil {
+ return fmt.Errorf("failed to invalidate older InfoFinder finding: %v", err)
+ }
+
+ // Get all findings which aren't invalidated, and then update the asset's summary
+ asset, err := asp.client.GetAsset(ctx, assetScan.Asset.Id, models.GetAssetsAssetIDParams{})
+ if err != nil {
+ return fmt.Errorf("failed to get asset %s: %w", assetScan.Asset.Id, err)
+ }
+ if asset.Summary == nil {
+ asset.Summary = &models.ScanFindingsSummary{}
+ }
+
+ totalInfoFinder, err := asp.getActiveFindingsByType(ctx, "InfoFinder", assetScan.Asset.Id)
+ if err != nil {
+ return fmt.Errorf("failed to get active info finder findings: %w", err)
+ }
+ asset.Summary.TotalInfoFinder = &totalInfoFinder
+
+ err = asp.client.PatchAsset(ctx, asset, assetScan.Asset.Id)
+ if err != nil {
+ return fmt.Errorf("failed to patch asset %s: %w", assetScan.Asset.Id, err)
+ }
+
+ return nil
+}
diff --git a/pkg/orchestrator/assetscanprocessor/misconfigurations.go b/pkg/orchestrator/assetscanprocessor/misconfigurations.go
index 101e54757..044de6b01 100644
--- a/pkg/orchestrator/assetscanprocessor/misconfigurations.go
+++ b/pkg/orchestrator/assetscanprocessor/misconfigurations.go
@@ -147,7 +147,7 @@ func (asp *AssetScanProcessor) reconcileResultMisconfigurationsToFindings(ctx co
totalMisconfigurations, err := asp.getActiveFindingsByType(ctx, "Misconfiguration", assetScan.Asset.Id)
if err != nil {
- return fmt.Errorf("failed to list active critial vulnerabilities: %w", err)
+ return fmt.Errorf("failed to get active misconfiguration findings: %w", err)
}
asset.Summary.TotalMisconfigurations = &totalMisconfigurations
diff --git a/pkg/orchestrator/assetscanprocessor/packages.go b/pkg/orchestrator/assetscanprocessor/packages.go
index 2d8676d22..6df44fc5f 100644
--- a/pkg/orchestrator/assetscanprocessor/packages.go
+++ b/pkg/orchestrator/assetscanprocessor/packages.go
@@ -146,7 +146,7 @@ func (asp *AssetScanProcessor) reconcileResultPackagesToFindings(ctx context.Con
totalPackages, err := asp.getActiveFindingsByType(ctx, "Package", assetScan.Asset.Id)
if err != nil {
- return fmt.Errorf("failed to list active critial vulnerabilities: %w", err)
+ return fmt.Errorf("failed to get active package findings: %w", err)
}
asset.Summary.TotalPackages = &totalPackages
diff --git a/pkg/orchestrator/assetscanprocessor/processor.go b/pkg/orchestrator/assetscanprocessor/processor.go
index dd377c1da..52cb2ddb8 100644
--- a/pkg/orchestrator/assetscanprocessor/processor.go
+++ b/pkg/orchestrator/assetscanprocessor/processor.go
@@ -110,6 +110,12 @@ func (asp *AssetScanProcessor) Reconcile(ctx context.Context, event AssetScanRec
}
}
+ if statusCompletedWithNoErrors(assetScan.Status.InfoFinder) {
+ if err := asp.reconcileResultInfoFindersToFindings(ctx, assetScan); err != nil {
+ return newFailedToReconcileTypeError(err, "infoFinder")
+ }
+ }
+
// Mark post-processing completed for this asset scan
assetScan.FindingsProcessed = utils.PointerTo(true)
err = asp.client.PatchAssetScan(ctx, assetScan, *assetScan.Id)
diff --git a/pkg/orchestrator/assetscanprocessor/rootkits.go b/pkg/orchestrator/assetscanprocessor/rootkits.go
index e300c7aea..5c6e2136f 100644
--- a/pkg/orchestrator/assetscanprocessor/rootkits.go
+++ b/pkg/orchestrator/assetscanprocessor/rootkits.go
@@ -142,7 +142,7 @@ func (asp *AssetScanProcessor) reconcileResultRootkitsToFindings(ctx context.Con
totalRootkits, err := asp.getActiveFindingsByType(ctx, "Rootkit", assetScan.Asset.Id)
if err != nil {
- return fmt.Errorf("failed to list active critial vulnerabilities: %w", err)
+ return fmt.Errorf("failed to get active rootkit findings: %w", err)
}
asset.Summary.TotalRootkits = &totalRootkits
diff --git a/pkg/orchestrator/assetscanprocessor/secrets.go b/pkg/orchestrator/assetscanprocessor/secrets.go
index 02163ac4c..2352f2c87 100644
--- a/pkg/orchestrator/assetscanprocessor/secrets.go
+++ b/pkg/orchestrator/assetscanprocessor/secrets.go
@@ -146,7 +146,7 @@ func (asp *AssetScanProcessor) reconcileResultSecretsToFindings(ctx context.Cont
totalSecrets, err := asp.getActiveFindingsByType(ctx, "Secret", assetScan.Asset.Id)
if err != nil {
- return fmt.Errorf("failed to list active critial vulnerabilities: %w", err)
+ return fmt.Errorf("failed to get active secret findings: %w", err)
}
asset.Summary.TotalSecrets = &totalSecrets
diff --git a/pkg/orchestrator/assetscanwatcher/families.go b/pkg/orchestrator/assetscanwatcher/families.go
index de0871885..51a3cf686 100644
--- a/pkg/orchestrator/assetscanwatcher/families.go
+++ b/pkg/orchestrator/assetscanwatcher/families.go
@@ -24,6 +24,7 @@ import (
"github.com/openclarity/vmclarity/pkg/shared/families/exploits"
exploitsCommon "github.com/openclarity/vmclarity/pkg/shared/families/exploits/common"
exploitdbConfig "github.com/openclarity/vmclarity/pkg/shared/families/exploits/exploitdb/config"
+ infofinderTypes "github.com/openclarity/vmclarity/pkg/shared/families/infofinder/types"
"github.com/openclarity/vmclarity/pkg/shared/families/malware"
clamconfig "github.com/openclarity/vmclarity/pkg/shared/families/malware/clam/config"
malwarecommon "github.com/openclarity/vmclarity/pkg/shared/families/malware/common"
@@ -189,6 +190,23 @@ func withMisconfigurationConfig(config *models.MisconfigurationsConfig, opts *Sc
}
}
+func withInfoFinderConfig(config *models.InfoFinderConfig, _ *ScannerConfig) FamiliesConfigOption {
+ return func(c *families.Config) {
+ if !config.IsEnabled() {
+ return
+ }
+
+ c.InfoFinder = infofinderTypes.Config{
+ Enabled: true,
+ ScannersList: config.GetScannersList(),
+ Inputs: nil, // rootfs directory will be determined by the CLI after mount.
+ ScannersConfig: infofinderTypes.ScannersConfig{
+ SSHTopology: infofinderTypes.SSHTopologyConfig{},
+ },
+ }
+ }
+}
+
func withRootkitsConfig(config *models.RootkitsConfig, opts *ScannerConfig) FamiliesConfigOption {
return func(c *families.Config) {
if !config.IsEnabled() {
@@ -219,6 +237,7 @@ func NewFamiliesConfigFrom(config *ScannerConfig, sfc *models.ScanFamiliesConfig
withMalwareConfig(sfc.Malware, config),
withMisconfigurationConfig(sfc.Misconfigurations, config),
withRootkitsConfig(sfc.Rootkits, config),
+ withInfoFinderConfig(sfc.InfoFinder, config),
}
for _, o := range opts {
diff --git a/pkg/orchestrator/scanconfigwatcher/helpers.go b/pkg/orchestrator/scanconfigwatcher/helpers.go
index 0a9b2b8c6..8fac14771 100644
--- a/pkg/orchestrator/scanconfigwatcher/helpers.go
+++ b/pkg/orchestrator/scanconfigwatcher/helpers.go
@@ -43,6 +43,7 @@ func newScanFromScanConfig(scanConfig *models.ScanConfig) *models.Scan {
TotalPackages: utils.PointerTo(0),
TotalRootkits: utils.PointerTo(0),
TotalSecrets: utils.PointerTo(0),
+ TotalInfoFinder: utils.PointerTo(0),
TotalVulnerabilities: &models.VulnerabilityScanSummary{
TotalCriticalVulnerabilities: utils.PointerTo(0),
TotalHighVulnerabilities: utils.PointerTo(0),
diff --git a/pkg/orchestrator/scanwatcher/helpers.go b/pkg/orchestrator/scanwatcher/helpers.go
index 9698bc965..2ed8238c7 100644
--- a/pkg/orchestrator/scanwatcher/helpers.go
+++ b/pkg/orchestrator/scanwatcher/helpers.go
@@ -41,6 +41,7 @@ func newAssetScanSummary() *models.ScanFindingsSummary {
TotalPackages: utils.PointerTo[int](0),
TotalRootkits: utils.PointerTo[int](0),
TotalSecrets: utils.PointerTo[int](0),
+ TotalInfoFinder: utils.PointerTo[int](0),
TotalVulnerabilities: newVulnerabilityScanSummary(),
}
}
@@ -98,6 +99,10 @@ func newAssetScanFromScan(scan *models.Scan, assetID string) (*models.AssetScan,
Errors: nil,
State: getInitStateFromFamilyConfig(familiesConfig.Vulnerabilities),
},
+ InfoFinder: &models.AssetScanState{
+ Errors: nil,
+ State: getInitStateFromFamilyConfig(familiesConfig.InfoFinder),
+ },
},
ResourceCleanup: utils.PointerTo(models.ResourceCleanupStatePending),
}, nil
@@ -121,6 +126,7 @@ func newScanSummary() *models.ScanSummary {
TotalPackages: utils.PointerTo(0),
TotalRootkits: utils.PointerTo(0),
TotalSecrets: utils.PointerTo(0),
+ TotalInfoFinder: utils.PointerTo(0),
TotalVulnerabilities: &models.VulnerabilityScanSummary{
TotalCriticalVulnerabilities: utils.PointerTo(0),
TotalHighVulnerabilities: utils.PointerTo(0),
@@ -156,6 +162,7 @@ func updateScanSummaryFromAssetScan(scan *models.Scan, result models.AssetScan)
case models.AssetScanStateStateDone:
s.JobsCompleted = utils.PointerTo(*s.JobsCompleted + 1)
s.TotalExploits = utils.PointerTo(*s.TotalExploits + *r.TotalExploits)
+ s.TotalInfoFinder = utils.PointerTo(*s.TotalInfoFinder + *r.TotalInfoFinder)
s.TotalMalware = utils.PointerTo(*s.TotalMalware + *r.TotalMalware)
s.TotalMisconfigurations = utils.PointerTo(*s.TotalMisconfigurations + *r.TotalMisconfigurations)
s.TotalPackages = utils.PointerTo(*s.TotalPackages + *r.TotalPackages)
diff --git a/pkg/orchestrator/scanwatcher/helpers_test.go b/pkg/orchestrator/scanwatcher/helpers_test.go
index 45392d338..7602c4a3b 100644
--- a/pkg/orchestrator/scanwatcher/helpers_test.go
+++ b/pkg/orchestrator/scanwatcher/helpers_test.go
@@ -61,6 +61,10 @@ func TestNewAssetScanFromScan(t *testing.T) {
Vulnerabilities: &models.VulnerabilitiesConfig{
Enabled: utils.PointerTo(true),
},
+ InfoFinder: &models.InfoFinderConfig{
+ Enabled: utils.PointerTo(true),
+ Scanners: utils.PointerTo([]string{"test"}),
+ },
},
},
},
@@ -104,6 +108,9 @@ func TestNewAssetScanFromScan(t *testing.T) {
Errors: nil,
State: utils.PointerTo(models.AssetScanStateStatePending),
},
+ InfoFinder: &models.AssetScanState{
+ State: utils.PointerTo(models.AssetScanStateStatePending),
+ },
},
Summary: newAssetScanSummary(),
Asset: &models.AssetRelationship{
@@ -125,6 +132,10 @@ func TestNewAssetScanFromScan(t *testing.T) {
Vulnerabilities: &models.VulnerabilitiesConfig{
Enabled: utils.PointerTo(true),
},
+ InfoFinder: &models.InfoFinderConfig{
+ Enabled: utils.PointerTo(true),
+ Scanners: utils.PointerTo([]string{"test"}),
+ },
},
},
},
diff --git a/pkg/shared/families/infofinder/sshtopology/scanner.go b/pkg/shared/families/infofinder/sshtopology/scanner.go
index dbbb802e8..6a0507068 100644
--- a/pkg/shared/families/infofinder/sshtopology/scanner.go
+++ b/pkg/shared/families/infofinder/sshtopology/scanner.go
@@ -79,13 +79,18 @@ func (s *Scanner) Run(sourceType utils.SourceType, userInput string) error {
errorsChan := make(chan error)
fingerprintsChan := make(chan []types.Info)
+ var chanWg sync.WaitGroup
+ chanWg.Add(1)
go func() {
+ defer chanWg.Done()
for fingerprints := range fingerprintsChan {
retResults.Infos = append(retResults.Infos, fingerprints...)
}
}()
+ chanWg.Add(1)
go func() {
+ defer chanWg.Done()
for e := range errorsChan {
errs = append(errs, e)
}
@@ -139,6 +144,7 @@ func (s *Scanner) Run(sourceType utils.SourceType, userInput string) error {
wg.Wait()
close(errorsChan)
close(fingerprintsChan)
+ chanWg.Wait()
retErr := errors.Join(errs...)
if retErr != nil {
diff --git a/pkg/shared/findingkey/infofinder.go b/pkg/shared/findingkey/infofinder.go
new file mode 100644
index 000000000..f36fd15b1
--- /dev/null
+++ b/pkg/shared/findingkey/infofinder.go
@@ -0,0 +1,42 @@
+// Copyright © 2023 Cisco Systems, Inc. and its affiliates.
+// All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package findingkey
+
+import (
+ "fmt"
+
+ "github.com/openclarity/vmclarity/api/models"
+)
+
+type InfoFinderKey struct {
+ ScannerName string
+ Type string
+ Data string
+ Path string
+}
+
+func (k InfoFinderKey) String() string {
+ return fmt.Sprintf("%s.%s.%s.%s", k.ScannerName, k.Type, k.Data, k.Path)
+}
+
+func GenerateInfoFinderKey(info models.InfoFinderFindingInfo) InfoFinderKey {
+ return InfoFinderKey{
+ ScannerName: *info.ScannerName,
+ Type: string(*info.Type),
+ Data: *info.Data,
+ Path: *info.Path,
+ }
+}
diff --git a/pkg/shared/findingkey/infofinder_test.go b/pkg/shared/findingkey/infofinder_test.go
new file mode 100644
index 000000000..a31095553
--- /dev/null
+++ b/pkg/shared/findingkey/infofinder_test.go
@@ -0,0 +1,98 @@
+// Copyright © 2023 Cisco Systems, Inc. and its affiliates.
+// All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package findingkey
+
+import (
+ "reflect"
+ "testing"
+
+ "github.com/openclarity/vmclarity/api/models"
+ "github.com/openclarity/vmclarity/pkg/shared/utils"
+)
+
+func TestGenerateInfoFinderKey(t *testing.T) {
+ type args struct {
+ info models.InfoFinderFindingInfo
+ }
+ tests := []struct {
+ name string
+ args args
+ want InfoFinderKey
+ }{
+ {
+ name: "sanity",
+ args: args{
+ info: models.InfoFinderFindingInfo{
+ Data: utils.PointerTo("data"),
+ Path: utils.PointerTo("path"),
+ ScannerName: utils.PointerTo("scanner"),
+ Type: utils.PointerTo(models.InfoTypeSSHAuthorizedKeyFingerprint),
+ },
+ },
+ want: InfoFinderKey{
+ ScannerName: "scanner",
+ Type: string(models.InfoTypeSSHAuthorizedKeyFingerprint),
+ Data: "data",
+ Path: "path",
+ },
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if got := GenerateInfoFinderKey(tt.args.info); !reflect.DeepEqual(got, tt.want) {
+ t.Errorf("GenerateInfoFinderKey() = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
+
+func TestInfoFinderKey_String(t *testing.T) {
+ type fields struct {
+ ScannerName string
+ Type string
+ Data string
+ Path string
+ }
+ tests := []struct {
+ name string
+ fields fields
+ want string
+ }{
+ {
+ name: "sanity",
+ fields: fields{
+ ScannerName: "scanner",
+ Type: string(models.InfoTypeSSHAuthorizedKeyFingerprint),
+ Data: "data",
+ Path: "path",
+ },
+ want: "scanner.SSHAuthorizedKeyFingerprint.data.path",
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ k := InfoFinderKey{
+ ScannerName: tt.fields.ScannerName,
+ Type: tt.fields.Type,
+ Data: tt.fields.Data,
+ Path: tt.fields.Path,
+ }
+ if got := k.String(); got != tt.want {
+ t.Errorf("String() = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
diff --git a/pkg/uibackend/rest/dashboard_riskiest_assets.go b/pkg/uibackend/rest/dashboard_riskiest_assets.go
index 4b0931388..971f318fe 100644
--- a/pkg/uibackend/rest/dashboard_riskiest_assets.go
+++ b/pkg/uibackend/rest/dashboard_riskiest_assets.go
@@ -261,7 +261,7 @@ func getCountForFindingType(summary *backendmodels.ScanFindingsSummary, findingT
return summary.TotalRootkits, nil
case backendmodels.SECRET:
return summary.TotalSecrets, nil
- case backendmodels.VULNERABILITY, backendmodels.SBOM:
+ case backendmodels.INFOFINDER, backendmodels.VULNERABILITY, backendmodels.SBOM:
fallthrough
default:
return nil, fmt.Errorf("unsupported finding type: %v", findingType)
@@ -282,7 +282,7 @@ func getTotalFindingFieldName(findingType backendmodels.ScanType) (string, error
return totalSecretsSummaryFieldName, nil
case backendmodels.VULNERABILITY:
return totalVulnerabilitiesSummaryFieldName, nil
- case backendmodels.SBOM:
+ case backendmodels.INFOFINDER, backendmodels.SBOM:
fallthrough
default:
return "", fmt.Errorf("unsupported finding type: %v", findingType)
diff --git a/pkg/uibackend/rest/dashboard_riskiest_assets_test.go b/pkg/uibackend/rest/dashboard_riskiest_assets_test.go
index ac4195246..14eb882fe 100644
--- a/pkg/uibackend/rest/dashboard_riskiest_assets_test.go
+++ b/pkg/uibackend/rest/dashboard_riskiest_assets_test.go
@@ -198,6 +198,17 @@ func Test_getCount(t *testing.T) {
want: utils.PointerTo(1),
wantErr: false,
},
+ {
+ name: "TotalInfoFinder - unsupported",
+ args: args{
+ summary: &backendmodels.ScanFindingsSummary{
+ TotalInfoFinder: utils.PointerTo(1),
+ },
+ findingType: backendmodels.INFOFINDER,
+ },
+ want: nil,
+ wantErr: true,
+ },
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
diff --git a/ui/src/layout/Scans/Configurations/ConfigurationsTable/index.js b/ui/src/layout/Scans/Configurations/ConfigurationsTable/index.js
index e5cf129a1..067e6962c 100644
--- a/ui/src/layout/Scans/Configurations/ConfigurationsTable/index.js
+++ b/ui/src/layout/Scans/Configurations/ConfigurationsTable/index.js
@@ -23,6 +23,7 @@ const SCAN_TYPES_FILTER_ITEMS = [
"misconfigurations",
"rootkits",
"secrets",
+ "infoFinder",
"sbom"
].map(type => ({value: `scanTemplate.assetScanTemplate.scanFamiliesConfig.${type}.enabled`, label: toCapitalized(type)}));
@@ -73,7 +74,8 @@ const ConfigurationsTable = () => {
"scanTemplate.assetScanTemplate.scanFamiliesConfig.rootkits.enabled",
"scanTemplate.assetScanTemplate.scanFamiliesConfig.sbom.enabled",
"scanTemplate.assetScanTemplate.scanFamiliesConfig.secrets.enabled",
- "scanTemplate.assetScanTemplate.scanFamiliesConfig.vulnerabilities.enabled"
+ "scanTemplate.assetScanTemplate.scanFamiliesConfig.vulnerabilities.enabled",
+ "scanTemplate.assetScanTemplate.scanFamiliesConfig.infoFinder.enabled"
],
Cell: ({row}) => {
const {scanFamiliesConfig} = row.original.scanTemplate.assetScanTemplate;
diff --git a/ui/src/layout/Scans/ScanConfigWizardModal/StepScanTypes.js b/ui/src/layout/Scans/ScanConfigWizardModal/StepScanTypes.js
index 2fd8305cc..d40f82bc3 100644
--- a/ui/src/layout/Scans/ScanConfigWizardModal/StepScanTypes.js
+++ b/ui/src/layout/Scans/ScanConfigWizardModal/StepScanTypes.js
@@ -12,6 +12,7 @@ const StepScanTypes = () => {
+
)
}
diff --git a/ui/src/layout/Scans/ScanConfigWizardModal/index.js b/ui/src/layout/Scans/ScanConfigWizardModal/index.js
index 854156b86..74477d8ed 100644
--- a/ui/src/layout/Scans/ScanConfigWizardModal/index.js
+++ b/ui/src/layout/Scans/ScanConfigWizardModal/index.js
@@ -31,6 +31,7 @@ const ScanConfigWizardModal = ({initialData, onClose, onSubmitSuccess}) => {
rootkits: {enabled: false},
secrets: {enabled: false},
misconfigurations: {enabled: false},
+ infoFinder: {enabled: false},
exploits: {enabled: false}
},
scanTemplate: {
@@ -44,6 +45,7 @@ const ScanConfigWizardModal = ({initialData, onClose, onSubmitSuccess}) => {
rootkits: {enabled: false},
secrets: {enabled: false},
misconfigurations: {enabled: false},
+ infoFinder: {enabled: false},
exploits: {enabled: false}
},
scannerInstanceCreationConfig: {