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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Please avoid adding duplicate information across this changelog and JIRA/doc inp
### Deprecated Features

### Technical Changes
- ROX-12967: Re-introduce `rpm` to the main image in order to be able parse installed packages on RHCOS nodes (from Compliance container)

## [3.73.1]

Expand Down
19 changes: 13 additions & 6 deletions compliance/collection/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
"github.com/cenkalti/backoff/v3"
"github.com/pkg/errors"
"github.com/stackrox/rox/compliance/collection/auditlog"
"github.com/stackrox/rox/compliance/collection/nodescanv2"
"github.com/stackrox/rox/compliance/collection/nodeinventorizer"
"github.com/stackrox/rox/generated/internalapi/sensor"
"github.com/stackrox/rox/generated/storage"
"github.com/stackrox/rox/pkg/clientconn"
Expand Down Expand Up @@ -148,7 +148,7 @@ func manageReceiveStream(ctx context.Context, cli sensor.ComplianceServiceClient
}
}

func manageNodeScanLoop(ctx context.Context, rescanInterval time.Duration, scanner nodescanv2.NodeScanner) <-chan *sensor.MsgFromCompliance {
func manageNodeScanLoop(ctx context.Context, rescanInterval time.Duration, scanner nodeinventorizer.NodeInventorizer) <-chan *sensor.MsgFromCompliance {
sensorC := make(chan *sensor.MsgFromCompliance)
nodeName := getNode()
go func() {
Expand Down Expand Up @@ -180,7 +180,7 @@ func manageNodeScanLoop(ctx context.Context, rescanInterval time.Duration, scann
return sensorC
}

func scanNode(nodeName string, scanner nodescanv2.NodeScanner) (*sensor.MsgFromCompliance, error) {
func scanNode(nodeName string, scanner nodeinventorizer.NodeInventorizer) (*sensor.MsgFromCompliance, error) {
result, err := scanner.Scan(nodeName)
if err != nil {
return nil, err
Expand Down Expand Up @@ -276,9 +276,16 @@ func main() {
defer close(sensorC)
go manageSendingToSensor(ctx, cli, sensorC)

// TODO(ROX-12971): Replace with real scanner
scanner := nodescanv2.FakeNodeScanner{}
nodeInventoriesC := manageNodeScanLoop(ctx, env.NodeRescanInterval.DurationSetting(), &scanner)
var scanner nodeinventorizer.NodeInventorizer
if features.UseFakeNodeInventory.Enabled() {
log.Infof("Using FakeNodeInventorizer")
scanner = &nodeinventorizer.FakeNodeInventorizer{}
} else {
log.Infof("Using NodeInventoryCollector")
scanner = &nodeinventorizer.NodeInventoryCollector{}
}
nodeInventoriesC := manageNodeScanLoop(ctx, env.NodeRescanInterval.DurationSetting(), scanner)

// multiplex producers (nodeInventoriesC) into the output channel (sensorC)
go func() {
for {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package nodescanv2
package nodeinventorizer

import (
timestamp "github.com/gogo/protobuf/types"
Expand All @@ -11,12 +11,12 @@ var (
log = logging.LoggerForModule()
)

// FakeNodeScanner can be used to send fake messages that would be emitted by NodeInventory
type FakeNodeScanner struct {
// FakeNodeInventorizer can be used to send fake messages that would be emitted by NodeInventory
type FakeNodeInventorizer struct {
}

// Scan returns a fake message in the same format a real NodeInventory would produce
func (f *FakeNodeScanner) Scan(nodeName string) (*storage.NodeInventory, error) {
func (f *FakeNodeInventorizer) Scan(nodeName string) (*storage.NodeInventory, error) {
log.Infof("Generating fake scan result message...")
msg := &storage.NodeInventory{
NodeId: "",
Expand Down
80 changes: 80 additions & 0 deletions compliance/collection/nodeinventorizer/nodeinventory.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package nodeinventorizer

import (
timestamp "github.com/gogo/protobuf/types"
"github.com/stackrox/rox/generated/storage"
"github.com/stackrox/scanner/database"
scannerV1 "github.com/stackrox/scanner/generated/scanner/api/v1"
"github.com/stackrox/scanner/pkg/analyzer/nodes"
)

// NodeInventorizer is the interface that defines the interface a scanner must implement
type NodeInventorizer interface {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How come we changed the name from NodeScanner? I figure if we define a Scan function, NodeScanner makes sense. I don't think Inventorizer is a word, so I'm a bit skeptical using it to name this interface

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We originally planned this as NodeScanning V2, but later changed it to NodeInventory, to improve separation, because the changes were rather confusing, as NodeScanning is already used in multiple places in the codebase.
I decided to reflect these changes in the implementation as well, to make it more clear we're talking about a NodeInventory and not the already existing NodeScan.

I concur that the name doesn't feel common, as it's a nonstandard word.
I'd be happy to consider alternatives, because I currently don't know how we could call it otherwise (except reverting back to NodeScanner, which would also be an option). WDYT?

Scan(nodeName string) (*storage.NodeInventory, error)
}

// NodeInventoryCollector is an implementation of NodeInventorizer
type NodeInventoryCollector struct {
}

// Scan scans the current node and returns the results as storage.NodeInventory object
func (n *NodeInventoryCollector) Scan(nodeName string) (*storage.NodeInventory, error) {
log.Info("Started node inventory")
// uncertifiedRHEL is set to false, as scans are only supported on RHCOS for now,
// which only exists in certified versions
componentsHost, err := nodes.Analyze(nodeName, "/host/", false)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: hostComponents?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also maybe comment why we use false here

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, since we have a log to say when we finish, can we have a log to state when we start?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've updated the code, added a more detailed comment and update the loglevel

log.Info("Finished node inventory")
if err != nil {
log.Errorf("Error scanning node /host inventory: %v", err)
return nil, err
}
log.Debugf("Components found under /host: %v", componentsHost)

protoComponents := protoComponentsFromScanComponents(componentsHost)

m := &storage.NodeInventory{
NodeName: nodeName,
ScanTime: timestamp.TimestampNow(),
Components: protoComponents,
}
return m, nil
}

func protoComponentsFromScanComponents(c *nodes.Components) *scannerV1.Components {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe we discussed probably moving this inside the Scanner repo, right?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we did. I have created ROX-14029 to track this change for the future. Thank you for reminding me 👍


if c == nil {
return nil
}

// For now, we only care about RHEL components, but this must be extended once we support non-RHCOS
rhelComponents := convertRHELComponents(c.CertifiedRHELComponents)

protoComponents := &scannerV1.Components{
Namespace: c.OSNamespace.Name,
OsComponents: nil,
RhelComponents: rhelComponents,
LanguageComponents: nil,
}
return protoComponents
}

func convertRHELComponents(rc *database.RHELv2Components) []*scannerV1.RHELComponent {
if rc == nil || rc.Packages == nil {
log.Warn("No RHEL packages found in scan result")
return nil
}
v1rhelc := make([]*scannerV1.RHELComponent, 0, len(rc.Packages))
for _, rhelc := range rc.Packages {
v1rhelc = append(v1rhelc, &scannerV1.RHELComponent{
// TODO(ROX-13936): Find out if ID field is needed here
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: We need a unique ID here. For now, use loop index.
If no index is provided, it is set to 0. If IDs are not unique, results are overwritten in ScannerDB
Idea for long term: Hash(Name+Version) that yields int64.

More information: https://srox.slack.com/archives/C04213W844U/p1667825859123589

Name: rhelc.Name,
Namespace: rc.Dist,
Version: rhelc.Version,
Arch: rhelc.Arch,
Module: rhelc.Module,
Cpes: rc.CPEs,
Executables: rhelc.Executables,
})
}
return v1rhelc
}
20 changes: 0 additions & 20 deletions compliance/collection/nodescanv2/nodescan.go

This file was deleted.

23 changes: 0 additions & 23 deletions compliance/collection/nodescanv2/nodescan_test.go

This file was deleted.

3 changes: 2 additions & 1 deletion image/rhel/Dockerfile.envsubst
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ RUN ln -s entrypoint-wrapper.sh /stackrox/admission-control && \
microdnf clean all && \
rm /tmp/snappy.rpm /tmp/postgres.rpm /tmp/postgres-libs.rpm RPM-GPG-KEY-CentOS-Official && \
# (Optional) Remove line below to keep package management utilities
rpm -e --nodeps $(rpm -qa curl '*rpm*' '*dnf*' '*libsolv*' '*hawkey*' 'yum*') && \
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we removing this here? (I am not sure what this file does, that is why I am asking)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the spot where we remove all package managers from the main image.
I have only removed *RPM* from this line, as we want to keep RPM in the images for the rpmdb command Compliance needs.
IIUC, this file is the template from which the Dockerfile for main is generated.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, now I recall. We remove it from the removal code, so in fact we are keeping the package. All good

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Due to a lack of a follow-up // TODO(ROX-12345): comment, I have a question: where do we stand in this decision? Related Slack thread looks unfinished to me https://srox.slack.com/archives/C04213W844U/p1670426415333499

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Customers may ask about this (someone asked why Scanner still has it). Perhaps we should consider updating the docs or CHANGELOG? Definitely need to also make sure our default policy which allows Scanner to have RPM also allows our other images have it.

Perhaps a followup ticket, find a way to do this without rpm (eh. I wanted to remove it in Scanner, but it's the only official way to get rpmdb data), or perhaps use a separate Dockerfile for Compliance, so we minimize the number images with rpm installed

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have created a follow up ticket (ROX-13934) so we don't forget about it and will push this as a TODO comment.

# TODO(ROX-13934): Potentially add *RPM* to this list again
rpm -e --nodeps $(rpm -qa curl '*dnf*' '*libsolv*' '*hawkey*' 'yum*') && \
rm -rf /var/cache/dnf /var/cache/yum && \
# The contents of paths mounted as emptyDir volumes in Kubernetes are saved
# by the script `save-dir-contents` during the image build. The directory
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,8 @@ spec:
- mountPath: /run/secrets/stackrox.io/certs/
name: certs
readOnly: true
- mountPath: /tmp/
name: tmp-volume
volumes:
- hostPath:
path: /var/run/docker.sock
Expand Down Expand Up @@ -161,3 +163,5 @@ spec:
emptyDir: {}
- name: etc-pki-volume
emptyDir: {}
- name: tmp-volume
emptyDir: {}
4 changes: 4 additions & 0 deletions pkg/features/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ var (
// RHCOSNodeScanning enables phase 1 functions of "Full host level vulnerability scanning for RHCOS nodes" (ROX-10818)
RHCOSNodeScanning = registerFeature("Enable RHCOS node scanning of OS and installed packages", "ROX_RHCOS_NODE_SCANNING", false)

// UseFakeNodeInventory tells compliance to use FakeNodeScanner with hardcoded data instead of calling scanner.Analyze()
// TODO(ROX-13935): Remove this FF and the accompanying code
UseFakeNodeInventory = registerFeature("Enables compliance to use FakeNodeScanner with hardcoded data instead of calling scanner.Analyze()", "ROX_RHCOS_FAKE_NODE_INVENTORY", false)

// ProcessesListeningOnPort enables the NetworkFlow code to also update the processes that are listening on ports
ProcessesListeningOnPort = registerFeature("Enable Processes Listening on Port", "ROX_PROCESSES_LISTENING_ON_PORT", false)

Expand Down