Zum Inhalt springen

SBOMs mit Client-Tools erzeugen

Praktische CycloneDX-Workflows für Maven, Gradle, Poetry, npm und OCI-Clients, inklusive der Fälle, in denen Craftifact die SBOM automatisch binden kann.

Kurzpfad

  1. Erzeuge SBOMs als CycloneDX-JSON.
  2. Nutze bevorzugt das Sidecar-Muster, wenn das jeweilige Ökosystem es sauber unterstützt.
  3. Sonst publiziere zuerst Paket oder Image und lade die SBOM danach über die SBOM-Upload-API hoch.
  4. Nutze artifact_id für Paketartefakte und oci_digest zusammen mit repository für OCI-Inhalte.
  5. Eine 201-Antwort bedeutet, dass die SBOM angenommen und zur Verarbeitung eingeplant wurde.

Welches Muster zu welchem Client passt

  • Maven: aktuell der beste Fit. Lade ein -cyclonedx.json-Sidecar im selben GAV-Pfad wie das Primärartefakt hoch.
  • Gradle mit Publishing in Maven-Repositorys: dasselbe Muster wie bei Maven. Hänge das CycloneDX-JSON als klassifiziertes Sidecar an.
  • Poetry, Twine und npm: erzeuge das CycloneDX-JSON lokal, publiziere normal und lade die SBOM danach direkt zu Craftifact hoch.
  • Docker und OCI: pushe zuerst das Image, erzeuge das CycloneDX-JSON aus dem gepushten Image und lade es dann über den OCI-Digest hoch.
  • pip und Go sind hier bewusst nicht enthalten. Die aktuelle Dokumentation zeigt dort Installations- oder Proxy-Flows, aber keinen normalen Hosted-Publish-Workflow.

Gemeinsamer Upload-Endpunkt

Craftifact akzeptiert CycloneDX-JSON in der Referenz zur SBOM-Upload-API:

SBOM-API
https://packages.example.com/api/sbom/upload/

Verwende genau eine Zielreferenz:

  • artifact_id für Paketartefakte
  • oci_digest zusammen mit repository für OCI-Inhalte

Bei Paket-Ökosystemen wie Python und npm ist es am einfachsten, die Artifact-ID aus deiner Automation zu übergeben. Wenn du manuell im UI arbeitest, kannst du sie auch aus der URL der Artefakt-Detailansicht übernehmen.

Den exakten Request-Vertrag, die Response-Formate und copy-fertige Upload-Beispiele hält die SBOM-Upload-API-Referenz zentral. Die Workflow-Abschnitte unten ergänzen nur die Ökosystem-spezifischen Schritte rund um diesen gemeinsamen Endpunkt.

Maven

Für Maven ist der glatteste Weg aktuell, das CycloneDX-JSON während des Builds zu erzeugen und als klassifiziertes Sidecar im selben Repository-Vorgang mit auszuliefern.

pom.xml
<build>
  <plugins>
    <plugin>
      <groupId>org.cyclonedx</groupId>
      <artifactId>cyclonedx-maven-plugin</artifactId>
      <version>PLUGIN_VERSION</version>
      <executions>
        <execution>
          <phase>package</phase>
          <goals>
            <goal>makeBom</goal>
          </goals>
          <configuration>
            <outputFormat>json</outputFormat>
            <schemaVersion>1.6</schemaVersion>
          </configuration>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>

Die erzeugte Datei wird als artifact-version-cyclonedx.json angehängt. Genau dieses Muster erlaubt Craftifact, das SBOM-Sidecar im selben GAV-Pfad dem passenden Primärartefakt zuzuordnen.

mvn deploy
mvn deploy \
  -s settings.xml \
  -DaltDeploymentRepository=craftifact::https://packages.example.com/repository/maven/libs-release/

Gradle

Wenn Gradle in ein Maven-Hosted-Repository publiziert, nutze dieselbe Sidecar-Idee: führe eine CycloneDX-Task aus und hänge das resultierende JSON mit dem Classifier cyclonedx an.

build.gradle.kts
plugins {
  id("maven-publish")
  id("org.cyclonedx.bom") version "PLUGIN_VERSION"
}

publishing {
  publications {
    create<MavenPublication>("mavenJava") {
      from(components["java"])
      artifact(layout.buildDirectory.file("reports/cyclonedx/bom.json")) {
        builtBy(tasks.named("cyclonedxBom"))
        classifier = "cyclonedx"
        extension = "json"
      }
    }
  }
}

Der genaue Output-Pfad hängt von deiner Plugin-Konfiguration ab. Für Craftifact ist entscheidend, dass am Ende eine -cyclonedx.json-Datei neben dem eigentlichen Maven-Artefakt publiziert wird.

Poetry und Twine

Für Python-Publishing ist das übliche Muster zweiteilig:

  1. Paket wie gewohnt bauen und publizieren
  2. CycloneDX-JSON separat erzeugen und danach zu Craftifact für das publizierte Artefakt hochladen
Poetry build und publish
poetry build
poetry publish --repository craftifact

Ein praktikabler Generator ist cyclonedx-py:

CycloneDX-JSON erzeugen
cyclonedx-py poetry \
  --output-format json \
  --output-file dist/example-cyclonedx.json

Wenn du statt poetry publish mit Twine publizierst, bleibt der SBOM-Teil gleich:

Twine publish
twine upload \
  --repository-url https://packages.example.com/repository/python/libs-release/ \
  dist/*

Sobald das Paketartefakt in Craftifact vorhanden ist, kopierst du seine Artifact ID aus dem Detailbereich oder löst sie über die Artefakt-Suche API auf. Danach lädst du die SBOM mit dieser artifact_id hoch:

Python-SBOM hochladen
curl \
  -X POST \
  -H "Authorization: Bearer $CRAFTIFACT_TOKEN" \
  -H "Content-Type: application/json" \
  --data-binary @dist/example-cyclonedx.json \
  "https://packages.example.com/api/sbom/upload/?artifact_id=ARTIFACT_ID"

npm

Bei npm publizierst du zuerst und erzeugst die SBOM danach direkt mit dem npm-Client, wenn deine npm-Version npm sbom unterstützt.

npm publish
npm publish --registry https://packages.example.com/repository/npm/libs-release/
npm sbom
npm sbom --sbom-format cyclonedx > package-cyclonedx.json

Danach lädst du das erzeugte CycloneDX-JSON zum publizierten Artefakt hoch:

npm-SBOM hochladen
curl \
  -X POST \
  -H "Authorization: Bearer $CRAFTIFACT_TOKEN" \
  -H "Content-Type: application/json" \
  --data-binary @package-cyclonedx.json \
  "https://packages.example.com/api/sbom/upload/?artifact_id=ARTIFACT_ID"

Wenn dein npm-Setup npm sbom noch nicht anbietet, nutze stattdessen einen anderen CycloneDX-fähigen Generator in CI und lasse den Upload zu Craftifact unverändert.

Docker und OCI

Docker bleibt der normale Client für Build und Push, aber das praktikable SBOM-Muster ist heute getrennt:

  1. Image pushen
  2. CycloneDX-JSON aus dem gepushten Image erzeugen
  3. Dokument über den OCI-Digest zu Craftifact hochladen
docker push
docker push packages.example.com/libs-release/demo/app:1.0.0

Ein einfacher Generator ist syft:

OCI-SBOM erzeugen
syft registry:packages.example.com/libs-release/demo/app:1.0.0 \
  -o cyclonedx-json=image-cyclonedx.json

Nutze den Manifest-Digest aus der Push-Ausgabe oder aus einem nachgelagerten Inspect-Schritt und lade die SBOM dann hoch:

OCI-SBOM hochladen
curl \
  -X POST \
  -H "Authorization: Bearer $CRAFTIFACT_TOKEN" \
  -H "Content-Type: application/json" \
  --data-binary @image-cyclonedx.json \
  "https://packages.example.com/api/sbom/upload/?oci_digest=sha256:DIGEST&repository=libs-release"

So bleibt der Docker-Workflow vertraut, während Craftifact die SBOM trotzdem für den gepushten OCI-Inhalt speichern und verarbeiten kann.

Hochgeladene und generierte SBOMs

Craftifact kann mit SBOMs aus zwei Quellen arbeiten:

  • von dir bereitgestelltes CycloneDX-JSON, das du hochlädst oder als Sidecar publizierst,
  • von Craftifact generierte SBOMs für unterstützte Hosted-Inhalte.

Generierte SBOMs decken aktuell Hosted-Maven-Artefakte mit .jar, .war und .ear sowie Hosted-OCI-Manifeste oder -Indexes ab. Proxy-Repositorys werden übersprungen, und von dir bereitgestellte SBOMs haben Vorrang, wenn für dasselbe Subject bereits eine SBOM gebunden ist.

Generierte SBOMs können mit der Zeit Freshness-Status anzeigen. Wenn Generator-Version oder Schwachstellendatenbank veralten, behandle das Ergebnis als Triage-Kontext und aktualisiere es, bevor du es als abschließende Antwort nutzt.

Verwandte Seiten