Skip to content

ROX-12967: Fix RHCOS detection and namespace generation #1026

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
Jan 3, 2023
Merged
10 changes: 10 additions & 0 deletions ext/featurens/redhatrelease/redhatrelease.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ var (
oracleReleaseRegexp = regexp.MustCompile(`(?P<os>Oracle) (Linux Server release) (?P<version>[\d]+)`)
centosReleaseRegexp = regexp.MustCompile(`(?P<os>[^\s]*) (Linux release|release) (?P<version>[\d]+)`)
redhatReleaseRegexp = regexp.MustCompile(`(?P<os>Red Hat Enterprise Linux) (Client release|Server release|Workstation release|release) (?P<version>[\d]+)`)
rhcosReleaseRegexp = regexp.MustCompile(`(?P<os>Red Hat Enterprise Linux) (CoreOS release) (?P<version>[\d]+[\.]?[\d]*)`) // RHCOS can differ a lot between minor versions, so we also keep the minor for it
rockyReleaseRegexp = regexp.MustCompile(`(?P<os>Rocky) (Linux release) (?P<version>[\d]+)`)

// RequiredFilenames defines the names of the files required to identify the RHEL-based release.
Expand Down Expand Up @@ -89,6 +90,15 @@ func (d detector) Detect(files analyzer.Files, opts *featurens.DetectorOptions)
return namespace
}

// Attempt to match Red Hat CoreOS.
r = rhcosReleaseRegexp.FindStringSubmatch(string(f.Contents))
if len(r) == 4 {
return &database.Namespace{
Name: "rhcos:" + r[3],
VersionFormat: rpm.ParserName,
}
}

// Attempt to match Rocky.
r = rockyReleaseRegexp.FindStringSubmatch(string(f.Contents))
if len(r) == 4 {
Expand Down
6 changes: 6 additions & 0 deletions ext/featurens/redhatrelease/redhatrelease_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,12 @@ func TestDetector(t *testing.T) {
"etc/redhat-release": {Contents: []byte(`CentOS Linux release 8.3.2011`)},
}),
},
{
ExpectedNamespace: &database.Namespace{Name: "rhcos:4.11", VersionFormat: rpm.ParserName},
Files: tarutil.CreateNewLayerFiles(map[string]analyzer.FileData{
"etc/redhat-release": {Contents: []byte(`Red Hat Enterprise Linux CoreOS release 4.11`)},
}),
},
{
ExpectedNamespace: &database.Namespace{Name: "centos:7", VersionFormat: rpm.ParserName},
Files: tarutil.CreateNewLayerFiles(map[string]analyzer.FileData{
Expand Down
8 changes: 6 additions & 2 deletions pkg/analyzer/detection/detection.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,12 @@ func DetectComponents(name string, files analyzer.Files, parent *database.Layer,
var featureVersions []database.FeatureVersion
var rhelfeatures *database.RHELv2Components

if namespace != nil && wellknownnamespaces.IsRHELNamespace(namespace.Name) {
// This is a RHEL-based image that must be scanned in a certified manner.
// In the current state, RHCOS will always be handled as certified system.
// If no CPEs are found on RHCOS, a note needs to be added that informs users of it.
// Also, the bool logic can be refactored into a single UseCertifiedWorkflow function.
// TODO(ROX-13906, ROX-14028): Implement note and refactor
if namespace != nil && (wellknownnamespaces.IsRHELNamespace(namespace.Name) || wellknownnamespaces.IsRHCOSNamespace(namespace.Name)) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe for another time, but perhaps we can make something like UseCertifiedWorkflow which checks if the namespace is rhel or rhcos. Minimizes all the this || that

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That is a good idea!
I have added a follow up ticket (ROX-14028) to track the refactoring.

// This is a certified image that needs to be scanned differently.
// Use the RHELv2 scanner instead.
packages, cpes, err := rpm.ListFeatures(files)
if err != nil {
Expand Down
5 changes: 3 additions & 2 deletions pkg/analyzer/nodes/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ func Analyze(nodeName, rootFSdir string, uncertifiedRHEL bool) (*Components, err
c.OSNamespace, c.OSComponents, c.CertifiedRHELComponents, _, err =
detection.DetectComponents(nodeName, files, nil, nil, uncertifiedRHEL)
if err != nil {
return nil, nil
return nil, err
}
// File reading errors during analysis are not exposed to DetectComponents, hence we
// check it and if there were any we fail.
Expand All @@ -89,7 +89,7 @@ func extractFilesFromDirectory(root string, matcher matcher.PrefixMatcher) (*fil
files: make(map[string]*fileMetadata),
}
m := metrics.FileExtractionMetrics{}
for _, dir := range matcher.GetCommonPrefixDirs() {
for _, dir := range []string{"etc/", "usr/share/rpm", "var/lib/rpm"} { // TODO(ROX-13771): Use range matcher.GetCommonPrefixDirs() again after fixing
if err := n.addFiles(filepath.FromSlash(dir), matcher, &m); err != nil {
return nil, errors.Wrapf(err, "failed to match filesMap at %q (at %q)", dir, n.root)
}
Expand All @@ -99,6 +99,7 @@ func extractFilesFromDirectory(root string, matcher matcher.PrefixMatcher) (*fil
}

// addFiles searches the directory for files using the matcher and adds them to the file map.
// TODO(ROX-14050): Improve handling of symlinks - if possible, follow instead of ignoring them
func (n *filesMap) addFiles(dir string, matcher matcher.Matcher, m *metrics.FileExtractionMetrics) error {
logrus.WithFields(logrus.Fields{
"root": n.root,
Expand Down
12 changes: 10 additions & 2 deletions pkg/rpm/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ const (
// databaseDir is the directory where the RPM database is expected to be in
// the container filesystem.
databaseDir = "var/lib/rpm"

// rhcosDBDir is an alternative to databaseDir, as RHCOS saves its RPM DB in
// a different path that symlinks to var/lib/rpm and our approach to listing filesystem
// contents does not respect symlinks.
// TODO(ROX-14050): get rid of this when symlink support is added
rhcosDBDir = "usr/share/rpm"
)

var (
Expand Down Expand Up @@ -97,9 +103,9 @@ func init() {
// files known for the different backend we support. The paths are relative to
// root.
func DatabaseFiles() []string {
paths := make([]string, 0, databaseFiles.Cardinality())
paths := make([]string, 0, 2*databaseFiles.Cardinality())
for filename := range databaseFiles {
paths = append(paths, path.Join(databaseDir, filename))
paths = append(paths, path.Join(databaseDir, filename), path.Join(rhcosDBDir, filename))
}
return paths
}
Expand All @@ -117,6 +123,8 @@ func CreateDatabaseFromImage(imageFiles analyzer.Files) (*rpmDatabase, error) {
for name := range databaseFiles {
if data, exists := imageFiles.Get(path.Join(databaseDir, name)); exists {
dbFiles[name] = data
} else if rhcosData, rhcosExists := imageFiles.Get(path.Join(rhcosDBDir, name)); rhcosExists {
dbFiles[name] = rhcosData
}
}
if len(dbFiles) == 0 {
Expand Down
7 changes: 7 additions & 0 deletions pkg/wellknownnamespaces/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ func IsRHELNamespace(namespace string) bool {
return strings.HasPrefix(namespace, "rhel")
}

// IsRHCOSNamespace returns true if the given argument identifies an RHCOS namespace.
// The namespace is expected to be of form `namespacename:version` but only `namespacename` is required.
// For example: rhel:7, rhel:8, centos:8, ubuntu:14.04.
func IsRHCOSNamespace(namespace string) bool {
return strings.HasPrefix(namespace, "rhcos")
}

// IsUbuntuNamespace returns true if the given argument identifies an Ubuntu namespace.
// The namespace is expected to be of form `namespacename:version` but only `namespacename` is required.
// For example: rhel:7, rhel:8, centos:8, ubuntu:14.04.
Expand Down