diff --git a/CHANGELOG.md b/CHANGELOG.md index c2af32dcd6ab2..9b68044b6fb25 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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] diff --git a/compliance/collection/main.go b/compliance/collection/main.go index 078cd95758289..de53f03c0a83a 100644 --- a/compliance/collection/main.go +++ b/compliance/collection/main.go @@ -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" @@ -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() { @@ -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 @@ -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 { diff --git a/compliance/collection/nodescanv2/fake_nodescan.go b/compliance/collection/nodeinventorizer/fake_nodeinventory.go similarity index 82% rename from compliance/collection/nodescanv2/fake_nodescan.go rename to compliance/collection/nodeinventorizer/fake_nodeinventory.go index 59ac7f863080b..1ef2f1f1114d6 100644 --- a/compliance/collection/nodescanv2/fake_nodescan.go +++ b/compliance/collection/nodeinventorizer/fake_nodeinventory.go @@ -1,4 +1,4 @@ -package nodescanv2 +package nodeinventorizer import ( timestamp "github.com/gogo/protobuf/types" @@ -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: "", diff --git a/compliance/collection/nodeinventorizer/nodeinventory.go b/compliance/collection/nodeinventorizer/nodeinventory.go new file mode 100644 index 0000000000000..c5368bb99f7c7 --- /dev/null +++ b/compliance/collection/nodeinventorizer/nodeinventory.go @@ -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 { + 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) + 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 { + + 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 + Name: rhelc.Name, + Namespace: rc.Dist, + Version: rhelc.Version, + Arch: rhelc.Arch, + Module: rhelc.Module, + Cpes: rc.CPEs, + Executables: rhelc.Executables, + }) + } + return v1rhelc +} diff --git a/compliance/collection/nodescanv2/nodescan.go b/compliance/collection/nodescanv2/nodescan.go deleted file mode 100644 index 3885873680b96..0000000000000 --- a/compliance/collection/nodescanv2/nodescan.go +++ /dev/null @@ -1,20 +0,0 @@ -package nodescanv2 - -import ( - "github.com/pkg/errors" - "github.com/stackrox/rox/generated/storage" -) - -// NodeScanner defines an interface for V2 NodeScanning -type NodeScanner interface { - Scan(nodeName string) (*storage.NodeInventory, error) -} - -// NodeScan is the V2 NodeScanning implementation -type NodeScan struct { -} - -// Scan scans the current node and returns the results as storage.NodeInventory object -func (n *NodeScan) Scan(nodeName string) (*storage.NodeInventory, error) { - return nil, errors.New("Not implemented") -} diff --git a/compliance/collection/nodescanv2/nodescan_test.go b/compliance/collection/nodescanv2/nodescan_test.go deleted file mode 100644 index d47cd7bf9214a..0000000000000 --- a/compliance/collection/nodescanv2/nodescan_test.go +++ /dev/null @@ -1,23 +0,0 @@ -package nodescanv2 - -import ( - "testing" - - "github.com/stackrox/rox/generated/storage" - "github.com/stretchr/testify/suite" -) - -func TestNodeScan(t *testing.T) { - suite.Run(t, &NodeScanSuite{}) -} - -type NodeScanSuite struct { - suite.Suite -} - -func (n *NodeScanSuite) TestMessageFormat() { - fns, err := (&FakeNodeScanner{}).Scan("someNode") - n.Nil(err) - n.NotNil(fns) - n.IsType(&storage.NodeInventory{}, fns) -} diff --git a/image/rhel/Dockerfile.envsubst b/image/rhel/Dockerfile.envsubst index cc6921c2a5ecf..639e9fa592d2f 100644 --- a/image/rhel/Dockerfile.envsubst +++ b/image/rhel/Dockerfile.envsubst @@ -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*') && \ + # 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 diff --git a/image/templates/helm/stackrox-secured-cluster/templates/collector.yaml b/image/templates/helm/stackrox-secured-cluster/templates/collector.yaml index 4997dea064696..dca3cf3635996 100644 --- a/image/templates/helm/stackrox-secured-cluster/templates/collector.yaml +++ b/image/templates/helm/stackrox-secured-cluster/templates/collector.yaml @@ -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 @@ -161,3 +163,5 @@ spec: emptyDir: {} - name: etc-pki-volume emptyDir: {} + - name: tmp-volume + emptyDir: {} diff --git a/pkg/features/list.go b/pkg/features/list.go index afc73d09cd90d..b96a6121bb24a 100644 --- a/pkg/features/list.go +++ b/pkg/features/list.go @@ -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)