Skip to content

Commit

Permalink
Fix OS fingerprinting, add tests, remove dead code
Browse files Browse the repository at this point in the history
Fix cases where OS fingerprinting differed too much from the old
recog-java matching.

Added tests for OS fingerprinting.

Added test for analyzing an image, including a fake alpine image which
only contains the os-release file and installed packages file.
  • Loading branch information
gschneider-r7 committed Jun 19, 2019
1 parent 931778f commit 3214fc7
Show file tree
Hide file tree
Showing 14 changed files with 236 additions and 247 deletions.
Binary file added fakealpine.tar
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package com.rapid7.container.analyzer.docker.os;

import com.rapid7.container.analyzer.docker.model.image.OperatingSystem;
import com.rapid7.container.analyzer.docker.model.image.Package;
import com.rapid7.container.analyzer.docker.model.image.PackageType;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
Expand All @@ -20,6 +18,9 @@
public class Fingerprinter {

private static final Pattern PATTERN = Pattern.compile("(?<name>.*)=(?<value>.*)");
private static final Pattern PHOTON_RELEASE = Pattern.compile("^(?i:VMWare Photon(?:\\s?OS)?(?:/)?(?:\\s?Linux)?\\s?(?:v)?(\\d+?(?:\\.\\d+?)*?)?)$");
private static final Pattern RHEL_RELEASE = Pattern.compile("^(?i:(?:Red Hat|RedHat|Red-Hat|RHEL)(?: Enterprise)?(?: Linux)?(?: Server)?(?: release)?(?: [a-z]+)?\\s?(\\d+?(?:\\.\\d+?)*?)?)(?:\\s?\\(.*\\))?$");
private static final String OS_FAMILY = "Linux";
private static final Map<String, String> OS_ID_TO_VENDOR = Arrays.stream(new String[][]{
{"alpine", "Alpine"},
{"amzn", "Amazon"},
Expand All @@ -35,10 +36,6 @@ public class Fingerprinter {
{"ubuntu", "Ubuntu"},
}).collect(toMap(kv -> kv[0], kv -> kv[1]));

public Fingerprinter() {

}

/**
* Parses the contents of an os-release file to ascertain the fingerprint of an operating system.
*
Expand Down Expand Up @@ -97,37 +94,25 @@ private OperatingSystem parseOsRelease(InputStream input, String architecture) t
}

String vendor = OS_ID_TO_VENDOR.get(id);
OperatingSystem operatingSystem = null;

// attempt to run recog against the name and version, which should be accurate already
if (product != null && !product.isEmpty() && version != null && !version.isEmpty())
operatingSystem = fingerprintOperatingSystem(product + " " + version, version, vendor, architecture);

// try with description if first try didn't match
if (operatingSystem == null && description != null)
operatingSystem = fingerprintOperatingSystem(description, version, vendor, architecture);

// try with product name if the description was null or didn't match
if (operatingSystem == null && product != null)
operatingSystem = fingerprintOperatingSystem(product, version, vendor, architecture);

// default to building a fingerprint with the distribution ID and release version
if (operatingSystem == null && product != null && version != null)
operatingSystem = new OperatingSystem(vendor == null ? trimProductName(product) : vendor, "Linux", "Linux", architecture, version, description);
if (!vendor.equals("VMWare"))
product = OS_FAMILY;

return operatingSystem;
return fingerprintOperatingSystem(vendor, product, version, architecture);
}
}

private OperatingSystem parseRhelFamilyRelease(InputStream input, String architecture) throws IOException {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8))) {
Matcher matcher = RHEL_RELEASE.matcher("");
String version = "";
String line = null;
String description = "";
while ((line = reader.readLine()) != null) {
description += line;
matcher.reset(line);
if (matcher.matches())
version = matcher.group(1);
}

return fingerprintOperatingSystem(description, null, null, architecture);
return fingerprintOperatingSystem("Red Hat", OS_FAMILY, version, architecture);
}
}

Expand All @@ -140,48 +125,31 @@ private OperatingSystem parseAlpineRelease(InputStream input, String architectur
version = line;
}

return fingerprintOperatingSystem("Alpine Linux", version, "Alpine", architecture);
return fingerprintOperatingSystem("Alpine", OS_FAMILY, version, architecture);
}
}

private OperatingSystem parsePhotonRelease(InputStream input, String architecture) throws IOException {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8))) {
Matcher matcher = PHOTON_RELEASE.matcher("");
String product = "Photon Linux";
String version = "";
String line = null;
String description = null;
while ((line = reader.readLine()) != null) {
if (line.startsWith("VMWare"))
description = line;
matcher.reset(line);
if (matcher.matches())
version = matcher.group(1);
}

return fingerprintOperatingSystem(description, null, "VMWare", architecture);
return fingerprintOperatingSystem("VMWare", product, version, architecture);
}
}

private String trimProductName(String productName) {
return productName.replaceAll("(?i:\\s+(?:Enterprise|(?:GNU/)?Linux).*)", "");
}

public Package fingerprintPackage(OperatingSystem operatingSystem, String pkg) {
private OperatingSystem fingerprintOperatingSystem(String vendor, String product, String version, String architecture) {
if (vendor.equals("VMWare"))
product = "Photon Linux";

Pattern pattern = Pattern.compile("(?<name>.*) (?<version>.*).*");
Matcher matcher = pattern.matcher(pkg);
if (matcher.matches()) {
String name = matcher.group("name");
String version = matcher.group("version");
return new Package("linux", PackageType.UNKNOWN, operatingSystem, name, version, pkg, 0L, null, null, null);
} else
return null;
return new OperatingSystem(vendor, OS_FAMILY, product, architecture, version, vendor + " " + product + " " + version);
}

public OperatingSystem fingerprintOperatingSystem(String productDescription, String productArchitecture) {
return fingerprintOperatingSystem(productDescription, null, null, productArchitecture);
}

public OperatingSystem fingerprintOperatingSystem(String productDescription, String productVersion, String productVendor, String productArchitecture) {
OperatingSystem operatingSystem = null;
if (productDescription == null || productDescription.isEmpty())
return null;

return operatingSystem;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package com.rapid7.container.analyzer.docker.os;

import com.rapid7.container.analyzer.docker.model.image.OperatingSystem;
import java.io.IOException;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;

class FingerprinterTest {

@Test
void parseAlpine() throws IOException {
// Given
Fingerprinter fp = new Fingerprinter();

// When
OperatingSystem os = fp.parse(FingerprinterTest.class.getResourceAsStream("alpine.txt"), "/etc/os-release", "x86_64");

// Then
assertEquals("Alpine", os.getVendor());
assertEquals("3.8.0", os.getVersion());
assertEquals("Alpine Linux 3.8.0", os.getDescription());
}

@Test
void parseAlpineRelease() throws IOException {
// Given
Fingerprinter fp = new Fingerprinter();

// When
OperatingSystem os = fp.parse(FingerprinterTest.class.getResourceAsStream("alpine-release.txt"), "/etc/alpine-release", "x86_64");

// Then
assertEquals("Alpine", os.getVendor());
assertEquals("3.8.0", os.getVersion());
assertEquals("Alpine Linux 3.8.0", os.getDescription());
}

@Test
void parseDebian() throws IOException {
// Given
Fingerprinter fp = new Fingerprinter();

// When
OperatingSystem os = fp.parse(FingerprinterTest.class.getResourceAsStream("debian.txt"), "/etc/os-release", "x86_64");

// Then
assertEquals("Debian", os.getVendor());
assertEquals("8", os.getVersion());
assertEquals("Debian Linux 8", os.getDescription());
}

@Test
void parseOracle() throws IOException {
// Given
Fingerprinter fp = new Fingerprinter();

// When
OperatingSystem os = fp.parse(FingerprinterTest.class.getResourceAsStream("oracle.txt"), "/etc/os-release", "x86_64");

// Then
assertEquals("Oracle", os.getVendor());
assertEquals("7.6", os.getVersion());
assertEquals("Oracle Linux 7.6", os.getDescription());
}

@Test
void parsePhoton() throws IOException {
// Given
Fingerprinter fp = new Fingerprinter();

// When
OperatingSystem os = fp.parse(FingerprinterTest.class.getResourceAsStream("photon.txt"), "/etc/os-release", "x86_64");

// Then
assertEquals("VMWare", os.getVendor());
assertEquals("3.0", os.getVersion());
assertEquals("VMWare Photon Linux 3.0", os.getDescription());
}

@Test
void parsePhotonRelease() throws IOException {
// Given
Fingerprinter fp = new Fingerprinter();

// When
OperatingSystem os = fp.parse(FingerprinterTest.class.getResourceAsStream("photon-release.txt"), "/etc/photon-release", "x86_64");

// Then
assertEquals("VMWare", os.getVendor());
assertEquals("3.0", os.getVersion());
assertEquals("VMWare Photon Linux 3.0", os.getDescription());
}

@Test
void parseRedhatRelease() throws IOException {
// Given
Fingerprinter fp = new Fingerprinter();

// When
OperatingSystem os = fp.parse(FingerprinterTest.class.getResourceAsStream("redhat-release.txt"), "/etc/redhat-release", "x86_64");

// Then
assertEquals("Red Hat", os.getVendor());
assertEquals("7.6", os.getVersion());
assertEquals("Red Hat Linux 7.6", os.getDescription());
}

@Test
void parseUbuntu() throws IOException {
// Given
Fingerprinter fp = new Fingerprinter();

// When
OperatingSystem os = fp.parse(FingerprinterTest.class.getResourceAsStream("ubuntu.txt"), "/etc/os-release", "x86_64");

// Then
assertEquals("Ubuntu", os.getVendor());
assertEquals("16.04", os.getVersion());
assertEquals("Ubuntu Linux 16.04", os.getDescription());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.rapid7.container.analyzer.docker.service;

import com.rapid7.container.analyzer.docker.model.image.Image;
import com.rapid7.container.analyzer.docker.model.image.ImageId;
import com.rapid7.container.analyzer.docker.model.image.OperatingSystem;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;

class DockerImageAnalyzerServiceTest {

@Test
public void test() throws IOException {
// Given
File tarFile = new File("fakealpine.tar");
ImageId expectedId = new ImageId("sha256:7be494284b1dea6cb2012a5ef99676b4ec22868d9ee235c60e48181542d70fd5");
OperatingSystem expectedOs = new OperatingSystem("Alpine", "Linux", "Linux", "x86_64", "3.8.0", "Alpine Linux 3.8.0");
long expectedSize = 119296;
long expectedLayers = 2;
long expectedPackages = 66;

// When
DockerImageAnalyzerService analyzer = new DockerImageAnalyzerService(null);
Path tmpdir = Files.createTempDirectory("r7dia");
Image image = analyzer.analyze(tarFile, tmpdir.toString());

// Then
assertEquals(expectedId, image.getId());
assertEquals(expectedOs, image.getOperatingSystem());
assertEquals(expectedSize, image.getSize());
assertEquals(expectedLayers, image.getLayers().size());
assertEquals(expectedPackages, image.getPackages().size());
}

}
Loading

0 comments on commit 3214fc7

Please sign in to comment.