Skip to content
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

feat(SBOMER-225): Adjust bom-ref for container image manifests #1012

Merged
merged 2 commits into from
Feb 10, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,11 @@ private void populateDependencies(List<Dependency> dependencies, List<Component>
}

components.forEach(component -> {
// Check that there isn't already a dependency with the bom-ref equals to the new purl, otherwise do not
// update it
if (!dependencies.stream().map(Dependency::getRef).toList().contains(component.getPurl())) {
component.setBomRef(component.getPurl());
}
dependencies.add(SbomUtils.createDependency(component.getBomRef()));
populateDependencies(dependencies, component.getComponents());
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ public Bom process(Bom bom) {
}
}

// If there are any purl relcoations, process these.
// If there are any purl relocations, process these.
purlRelocations.forEach((oldPurl, newPurl) -> updatePurl(bom, oldPurl, newPurl));

if (bom.getMetadata() != null && bom.getMetadata().getComponent() != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,14 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;

import org.cyclonedx.exception.ParseException;
import org.cyclonedx.model.Bom;
import org.cyclonedx.model.Component;
import org.cyclonedx.model.Dependency;
Expand Down Expand Up @@ -112,11 +115,17 @@ void shouldLeaveOnlyArchAndEpochQualifiers() throws IOException {
assertEquals(
"pkg:rpm/redhat/[email protected]?arch=x86_64&upstream=bzip2-1.0.8-8.el9.src.rpm&distro=rhel-9.2",
bom.getComponents().get(10).getPurl());

assertEquals(
"pkg:rpm/redhat/[email protected]_2.1?arch=x86_64&epoch=1&upstream=dbus-1.12.20-7.el9_2.1.src.rpm&distro=rhel-9.2",
bom.getComponents().get(21).getPurl());

assertEquals(
"pkg:rpm/redhat/[email protected]?arch=x86_64&upstream=bzip2-1.0.8-8.el9.src.rpm&distro=rhel-9.2&package-id=b2b140771748e045",
bom.getComponents().get(10).getBomRef());
assertEquals(
"pkg:rpm/redhat/[email protected]_2.1?arch=x86_64&epoch=1&upstream=dbus-1.12.20-7.el9_2.1.src.rpm&distro=rhel-9.2&package-id=55a4fbd380f817ef",
bom.getComponents().get(21).getBomRef());

assertEquals(0, SbomUtils.validate(SbomUtils.toJsonNode(bom)).size());

Bom adjusted = adjuster.adjust(bom);
Expand All @@ -126,6 +135,12 @@ void shouldLeaveOnlyArchAndEpochQualifiers() throws IOException {
assertEquals(
"pkg:rpm/redhat/[email protected]_2.1?arch=x86_64&epoch=1",
adjusted.getComponents().get(22).getPurl());

assertEquals("pkg:rpm/redhat/[email protected]?arch=x86_64", adjusted.getComponents().get(11).getBomRef());
assertEquals(
"pkg:rpm/redhat/[email protected]_2.1?arch=x86_64&epoch=1",
adjusted.getComponents().get(22).getBomRef());

assertEquals(0, SbomUtils.validate(SbomUtils.toJsonNode(adjusted)).size());
}

Expand Down Expand Up @@ -174,7 +189,6 @@ void depdsShouldPointToComponents() throws Exception {
assertEquals(0, SbomUtils.validate(SbomUtils.toJsonNode(bom)).size());

Bom adjusted = adjuster.adjust(bom);

// One component added (image) at position 0, one component removed (operating system), size is the same as
// before
assertEquals(459, adjusted.getComponents().size());
Expand Down Expand Up @@ -223,7 +237,9 @@ void metadataComponentShouldBeBareRepresentationOfMainComponent() throws IOExcep
// Has name
assertEquals("amq-streams/console-ui-rhel9", mainComponent.getName());
// Other things, for example bom-ref
assertEquals("3893910a10b83660", mainComponent.getBomRef());
assertEquals(
"pkg:oci/console-ui-rhel9@sha256%3Aee4e27734a21cc6b8a8597ef2af32822ad0b4677dbde0a794509f55cbaff5ab3?arch=amd64&os=linux&tag=2.7.0-8.1718294415",
mainComponent.getBomRef());

// Is valid
assertEquals(0, SbomUtils.validate(SbomUtils.toJsonNode(adjusted)).size());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -414,12 +414,18 @@ public static Tool createTool(String version) {
*/
public static void updatePurl(Bom bom, String oldPurl, String newPurl) {
// Update main component's purl
updatePurl(bom.getMetadata().getComponent(), oldPurl, newPurl);
if (updatePurl(bom.getMetadata().getComponent(), oldPurl, newPurl)) {
updateBomRef(bom, bom.getMetadata().getComponent(), oldPurl, newPurl);
}

// If we have any components (we really should!) then update these purls as well
if (bom.getComponents() != null) {
// Update all components' purls (if needed)
bom.getComponents().forEach(c -> updatePurl(c, oldPurl, newPurl));
bom.getComponents().forEach(c -> {
if (updatePurl(c, oldPurl, newPurl)) {
updateBomRef(bom, c, oldPurl, newPurl);
}
});
}
}

Expand All @@ -441,6 +447,62 @@ public static boolean updatePurl(Component component, String oldPurl, String new
return false;
}

/**
* Updates the bom-ref for the given component, and update the refs in the dependencies hierarchy, looking for
* nested dependencies and provides.
*
* @param component
* @param newRef
* @return
*/
public static void updateBomRef(Bom bom, Component component, String oldRef, String newRef) {
// Update the BOM reference of the component
// There might be cases (mainly for components detected by Syft) where the same purl is duplicated across
// components (which have different bom-refs), so we need to check if there are not already dependencies having
// the bom-ref equals to the new purl before updating it, otherwise we would have bom validation errors.
if (oldRef.equals(component.getBomRef())
&& !bom.getDependencies().stream().map(Dependency::getRef).toList().contains(newRef)) {

component.setBomRef(newRef);

// Recursively update the dependencies in the BOM
if (bom.getDependencies() != null) {
Set<Dependency> updatedDependencies = new TreeSet<>();
for (Dependency dependency : bom.getDependencies()) {
updateDependencyRef(dependency, oldRef, newRef);
updatedDependencies.add(dependency);
}
bom.setDependencies(new ArrayList<>(updatedDependencies));
}
}
}

private static void updateDependencyRef(Dependency dependency, String oldRef, String newRef) {
// If the current dependency has the oldRef, replace it with newRef
if (dependency.getRef().equals(oldRef)) {
Dependency updatedDependency = new Dependency(newRef);
updatedDependency.setDependencies(dependency.getDependencies());
updatedDependency.setProvides(dependency.getProvides());

// Replace the old dependency with the updated one
dependency = updatedDependency;
}

// Recursively update sub-dependencies
if (dependency.getDependencies() != null) {
for (Dependency subDependency : dependency.getDependencies()) {
updateDependencyRef(subDependency, oldRef, newRef);
}
}

// Recursively update provided dependencies
if (dependency.getProvides() != null) {
for (Dependency subProvide : dependency.getProvides()) {
updateDependencyRef(subProvide, oldRef, newRef);
}
}
}

public static ToolInformation createToolInformation(String version) {
ToolInformation information = new ToolInformation();
Component toolComponent = new Component();
Expand Down
Loading