You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by pk...@apache.org on 2026/03/29 18:44:02 UTC
(commons-build-plugin) branch feat/slsa updated (c244b62 -> 8ac265d)
This is an automated email from the ASF dual-hosted git repository.
pkarwasz pushed a change to branch feat/slsa
in repository https://gitbox.apache.org/repos/asf/commons-build-plugin.git
discard c244b62 Add `build-attestation` goal
discard 7d81877 Add SLSA Provenance model classes
add fbd7ce5 Typo
add 33fc376 No need to override project/issueManagement/system
add 566cb52 Bump github/codeql-action from 4.31.2 to 4.31.3 (#409)
add 6aa2fae Bump actions/dependency-review-action from 4.8.1 to 4.8.2 (#410)
add 1f5eb34 Bump org.apache.commons:commons-parent from 91 to 92 (#411)
add f8a37e3 Bump org.apache.commons:commons-parent from 91 to 92 #411
add 821185e Dependabot => quarterly
add fd8eef5 Use HTTPS to download XML Schema
add 39d0210 Bump CodeQL 4.31.4
add 1b29960 Bump actions/checkout from v5.0.0 to v6.0.0
add 2796cd6 Bump org.apache.commons:commons-parent from 92 to 93 (#412)
add a6aa4e3 Bump org.apache.commons:commons-parent from 92 to 93 #412
add d449f37 Fix Apache RAT plugin console warnings
add dc1bd64 PUt type first
add 8ce8483 Fix Apache RAT plugin console warnings
add 653e9d7 Use new oak leaf logo
add bcb7685 Bump github/codeql-action from 4.31.4 to 4.31.5
add c5046f7 Bump github/codeql-action from 4.31.5 to 4.31.6
add e18a60b Bump actions/checkout from 6.0.0 to 6.0.1
add f456334 Bump actions/setup-java from 5.0.0 to 5.1.0
add f29cccd Bump github/codeql-action from 4.31.6 to 4.31.7
add 801820e Ask for details on AI usage in creating a pull request
add 91928d7 Add NetBean IDE metadata files to .gitignore
add f6fe4ae Bump github/codeql-action from 4.31.7 to 4.31.8
add b3fa7d7 Bump actions/cache from 4.3.0 to 5.0.1
add 5cb8abe Bump github/codeql-action from 4.31.8 to 4.31.9
add db2d03a Bump notice file end year from 2025 to 2026
add be0ee94 Bump actions/upload-artifact from 5.0.0 to 6.0.0 (#413)
add 43d77e4 Bump actions/checkout from 5.0.0 to 6.0.1 (#414)
add 5b69970 Bump commons.maven.version from 3.9.11 to 3.9.12 (#415)
add 4200330 Bump commons.maven.version from 3.9.11 to 3.9.12 #415
add 30669fc Fix XML Schema xsi:schemaLocation name (as opposed to URI)
add 7e2dcee Remove unused and outdated social media links (#416)
add db7407b Bump org.apache.commons:commons-parent from 93 to 94
add ba36e66 Bump github/codeql-action from 4.31.9 to 4.31.10
add 79aa9a5 Bump org.apache.commons:commons-parent from 94 to 95.
add 7df03d2 Bump actions/setup-java from 5.1.0 to 5.2.0
add 1348404 Bump org.apache.commons:commons-parent from 95 to 96.
add afe66d2 Bump actions/checkout from 6.0.1 to 6.0.2
add 9c373bf Bump graalvm.version from 25.0.1 to 25.0.2
add 8aa599b Prepare for the next release candidate
add f19c245 Bump github/codeql-action from 4.31.10 to 4.31.11
add 3b892bf Bump github/codeql-action from 4.31.11 to 4.32.0
add 5c3ec90 Prepare for the release candidate 1.16.1 RC1
add 8ebc290 Bump to next development version
add 45ff91b Merge branch 'release'
add 1f6e260 Updates for the next release
add f73d6e7 Updates for the next release
add ea42880 Bump github/codeql-action from 4.32.0 to 4.32.2
add 9375965 Use max-parallel: 20 as asked by Apache Infra
add d6e66ed Bump actions/cache from 5.0.2 to 5.0.3
add 241a20a Bump github/codeql-action from 4.32.2 to 4.32.3
add e5b76bd Bump github/codeql-action from 4.32.0 to 4.32.4
add 3fdb0f1 Bump org.apache.commons:commons-parent from 96 to 97.
add 1e88e2e Bump github/codeql-action from 4.32.4 to 4.32.5
add 0bb40ee Bump github/codeql-action from 4.32.5 to 4.32.6
add 0547f11 Bump actions/upload-artifact from 6.0.0 to 7.0.0
add 800e87e Bump github/codeql-action from 4.32.6 to 4.34.0
add 6bda4a6 Bump github/codeql-action from 4.34.0 to 4.34.1
add e35c32a Add `commons-codec` snapshot to the dependencies
new 7ed5387 Add SLSA Provenance model classes
new 841d9f5 Add `build-attestation` goal
new 8ac265d Document Commons `buildType`
This update added new revisions after undoing existing revisions.
That is to say, some revisions that were in the old version of the
branch are not in the new version. This situation occurs
when a user --force pushes a change and generates a repository
containing something like this:
* -- * -- B -- O -- O -- O (c244b62)
\
N -- N -- N refs/heads/feat/slsa (8ac265d)
You should already have received notification emails for all of the O
revisions, and so the following emails describe only the N revisions
from the common base, B.
Any revisions marked "omit" are not gone; other references still
refer to them. Any revisions marked "discard" are gone forever.
The 3 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
Summary of changes:
.github/dependabot.yml | 6 +--
.github/pull_request_template.md | 2 +-
.github/workflows/codeql-analysis.yml | 2 +-
.github/workflows/dependency-review.yml | 4 +-
.github/workflows/maven.yml | 7 +--
.github/workflows/scorecards-analysis.yml | 6 +--
.gitignore | 4 ++
CONTRIBUTING.md | 3 +-
NOTICE.txt | 2 +-
README.md | 7 ++-
RELEASE-NOTES.txt | 44 +++++++++++++++
pom.xml | 60 ++++++++++++---------
src/changes/changes.xml | 23 +++++---
.../contributing-md-template.md | 3 +-
.../commons-xdoc-templates/readme-md-template.md | 1 -
src/media/commons-logo-component-100.xcf | Bin 0 -> 30021 bytes
src/media/commons-logo-component.xcf | Bin 0 -> 175524 bytes
src/media/logo.png | Bin 0 -> 10787 bytes
src/site/markdown/slsa/v0.1.0.md | 21 ++++++++
src/site/resources/images/logo.png | Bin 0 -> 10787 bytes
src/site/site.xml | 3 ++
src/site/xdoc/download_build-plugin.xml | 26 ++++-----
22 files changed, 154 insertions(+), 70 deletions(-)
create mode 100644 src/media/commons-logo-component-100.xcf
create mode 100644 src/media/commons-logo-component.xcf
create mode 100644 src/media/logo.png
create mode 100644 src/site/markdown/slsa/v0.1.0.md
create mode 100644 src/site/resources/images/logo.png
(commons-build-plugin) 02/03: Add `build-attestation` goal
Posted by pk...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
pkarwasz pushed a commit to branch feat/slsa
in repository https://gitbox.apache.org/repos/asf/commons-build-plugin.git
commit 841d9f50d9b2bde368c8d3e80dcdcc2e5c424467
Author: Piotr P. Karwasz <pi...@github.copernik.eu>
AuthorDate: Fri Mar 27 18:11:37 2026 +0100
Add `build-attestation` goal
This goal generates a [SLSA](https://slsa.dev/) build attestation and attaches it to the build as a file with the `.intoto.json` extension.
The attestation records the following information about the build environment:
- The Java version used (vendor, version string)
- The Maven version used
- The `gitTree` hash of the unpacked Java distribution
- The `gitTree` hash of the unpacked Maven distribution
The `gitTree` hashes uniquely and verifiably identify the exact content of the Java and Maven distributions used during the build, independently of how or where they were obtained. This allows consumers of the attestation to verify that the build
environment matches a known distribution.
---
pom.xml | 53 +++
.../apache/commons/build/BuildAttestationMojo.java | 363 +++++++++++++++++++++
.../commons/build/internal/ArtifactUtils.java | 82 +++++
.../build/internal/BuildToolDescriptors.java | 88 +++++
.../apache/commons/build/internal/GitUtils.java | 87 +++++
.../build/models/slsa/v1_2/BuildDefinition.java | 10 +-
.../commons/build/models/slsa/v1_2/Builder.java | 8 +-
.../commons/build/BuildAttestationMojoTest.java | 134 ++++++++
.../apache/commons/build/internal/MojoUtils.java | 70 ++++
src/test/resources/artifacts/artifact-jar.txt | 2 +
src/test/resources/artifacts/artifact-pom.txt | 2 +
11 files changed, 891 insertions(+), 8 deletions(-)
diff --git a/pom.xml b/pom.xml
index 408b7df..f1e7ef5 100644
--- a/pom.xml
+++ b/pom.xml
@@ -90,6 +90,7 @@
<commons.jackson.version>2.21.1</commons.jackson.version>
<commons.jackson.annotations.version>2.21</commons.jackson.annotations.version>
<commons.maven.version>3.9.12</commons.maven.version>
+ <commons.maven.scm.version>2.2.1</commons.maven.scm.version>
<!--
Define the following in ~/.m2/settings.xml in an active profile:
(or provide them on the command line)
@@ -127,6 +128,12 @@
<artifactId>jackson-annotations</artifactId>
<version>${commons.jackson.annotations.version}</version>
</dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.datatype</groupId>
+ <artifactId>jackson-datatype-jsr310</artifactId>
+ <version>${commons.jackson.version}</version>
+ <scope>runtime</scope>
+ </dependency>
<dependency>
<!-- Try to deal with https://bz.apache.org/bugzilla/show_bug.cgi?id=66951 -->
<groupId>org.apache.maven</groupId>
@@ -134,6 +141,11 @@
<version>${commons.maven.version}</version>
<scope>provided</scope>
</dependency>
+ <dependency>
+ <groupId>org.apache.maven.plugin-tools</groupId>
+ <artifactId>maven-plugin-annotations</artifactId>
+ <version>3.15.2</version>
+ </dependency>
<dependency>
<!-- Try to deal with https://bz.apache.org/bugzilla/show_bug.cgi?id=66951 -->
<groupId>org.apache.maven</groupId>
@@ -141,11 +153,52 @@
<version>${commons.maven.version}</version>
<scope>provided</scope>
</dependency>
+ <dependency>
+ <groupId>org.apache.maven</groupId>
+ <artifactId>maven-embedder</artifactId>
+ <version>${commons.maven.version}</version>
+ <scope>provided</scope>
+ </dependency>
<dependency>
<groupId>org.apache.maven.plugin-tools</groupId>
<artifactId>maven-script-ant</artifactId>
<version>3.15.2</version>
</dependency>
+ <dependency>
+ <groupId>org.apache.maven.scm</groupId>
+ <artifactId>maven-scm-manager-plexus</artifactId>
+ <version>${commons.maven.scm.version}</version>
+ <scope>compile</scope>
+ <exclusions>
+ <exclusion>
+ <groupId>org.codehaus.plexus</groupId>
+ <artifactId>plexus-container-default</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.maven.scm</groupId>
+ <artifactId>maven-scm-provider-gitexe</artifactId>
+ <version>${commons.maven.scm.version}</version>
+ <scope>runtime</scope>
+ </dependency>
+ <dependency>
+ <groupId>net.javacrumbs.json-unit</groupId>
+ <artifactId>json-unit-assertj</artifactId>
+ <version>2.40.1</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.junit.jupiter</groupId>
+ <artifactId>junit-jupiter-api</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <version>5.18.0</version>
+ <scope>test</scope>
+ </dependency>
</dependencies>
<build>
<!-- include the site goal to detect problems previously encountered with maven-plugin-plugin 3.9.0 -->
diff --git a/src/main/java/org/apache/commons/build/BuildAttestationMojo.java b/src/main/java/org/apache/commons/build/BuildAttestationMojo.java
new file mode 100644
index 0000000..d021d2d
--- /dev/null
+++ b/src/main/java/org/apache/commons/build/BuildAttestationMojo.java
@@ -0,0 +1,363 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.build;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.lang.management.ManagementFactory;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.time.OffsetDateTime;
+import java.time.ZoneOffset;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+import javax.inject.Inject;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import org.apache.commons.build.internal.ArtifactUtils;
+import org.apache.commons.build.internal.BuildToolDescriptors;
+import org.apache.commons.build.internal.GitUtils;
+import org.apache.commons.build.models.slsa.v1_2.BuildDefinition;
+import org.apache.commons.build.models.slsa.v1_2.BuildMetadata;
+import org.apache.commons.build.models.slsa.v1_2.Builder;
+import org.apache.commons.build.models.slsa.v1_2.Provenance;
+import org.apache.commons.build.models.slsa.v1_2.ResourceDescriptor;
+import org.apache.commons.build.models.slsa.v1_2.RunDetails;
+import org.apache.commons.build.models.slsa.v1_2.Statement;
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.execution.MavenExecutionRequest;
+import org.apache.maven.execution.MavenSession;
+import org.apache.maven.plugin.AbstractMojo;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.MojoFailureException;
+import org.apache.maven.plugins.annotations.LifecyclePhase;
+import org.apache.maven.plugins.annotations.Mojo;
+import org.apache.maven.plugins.annotations.Parameter;
+import org.apache.maven.plugins.annotations.ResolutionScope;
+import org.apache.maven.project.MavenProject;
+import org.apache.maven.project.MavenProjectHelper;
+import org.apache.maven.rtinfo.RuntimeInformation;
+import org.apache.maven.scm.CommandParameters;
+import org.apache.maven.scm.ScmException;
+import org.apache.maven.scm.ScmFileSet;
+import org.apache.maven.scm.command.info.InfoItem;
+import org.apache.maven.scm.command.info.InfoScmResult;
+import org.apache.maven.scm.manager.ScmManager;
+import org.apache.maven.scm.repository.ScmRepository;
+
+/**
+ * This plugin generates an in-toto attestation for all the artifacts
+ */
+@Mojo(name = "build-attestation", defaultPhase = LifecyclePhase.VERIFY, requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME)
+public class BuildAttestationMojo extends AbstractMojo {
+
+ private static final String ATTESTATION_EXTENSION = "intoto.json";
+
+ private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
+
+ static {
+ OBJECT_MAPPER.findAndRegisterModules();
+ OBJECT_MAPPER.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
+ }
+
+ @Parameter(defaultValue = "${project.scm.connection}", readonly = true)
+ private String scmConnectionUrl;
+
+ @Parameter(defaultValue = "${project.scm.developerConnection}", readonly = true)
+ private String scmDeveloperConnectionUrl;
+
+ @Parameter(defaultValue = "${project.scm.tag}", readonly = true)
+ private String scmTag;
+
+ @Parameter(defaultValue = "${maven.home}", readonly = true)
+ private File mavenHome;
+
+ /**
+ * Issue SCM actions at this local directory
+ */
+ @Parameter(property = "commons.build.scmDirectory", defaultValue = "${basedir}")
+ private File scmDirectory;
+
+ @Parameter(property = "commons.build.outputDirectory", defaultValue = "${project.build.directory}")
+ private File outputDirectory;
+
+ @Parameter(property = "commons.build.skipAttach")
+ private boolean skipAttach;
+
+ /**
+ * The current Maven project.
+ */
+ private final MavenProject project;
+
+ /**
+ * SCM manager to detect the Git revision.
+ */
+ private final ScmManager scmManager;
+
+ /**
+ * Runtime information
+ */
+ private final RuntimeInformation runtimeInformation;
+
+ /**
+ * The current Maven session, used to resolve plugin dependencies.
+ */
+ private final MavenSession session;
+
+ /**
+ * Helper to attach artifacts to the project.
+ */
+ private final MavenProjectHelper mavenProjectHelper;
+
+ @Inject
+ public BuildAttestationMojo(MavenProject project, ScmManager scmManager, RuntimeInformation runtimeInformation, MavenSession session,
+ MavenProjectHelper mavenProjectHelper) {
+ this.project = project;
+ this.scmManager = scmManager;
+ this.runtimeInformation = runtimeInformation;
+ this.session = session;
+ this.mavenProjectHelper = mavenProjectHelper;
+ }
+
+ void setOutputDirectory(File outputDirectory) {
+ this.outputDirectory = outputDirectory;
+ }
+
+ public File getScmDirectory() {
+ return scmDirectory;
+ }
+
+ public void setScmDirectory(File scmDirectory) {
+ this.scmDirectory = scmDirectory;
+ }
+
+ void setScmConnectionUrl(String scmConnectionUrl) {
+ this.scmConnectionUrl = scmConnectionUrl;
+ }
+
+ void setScmDeveloperConnectionUrl(String scmDeveloperConnectionUrl) {
+ this.scmDeveloperConnectionUrl = scmDeveloperConnectionUrl;
+ }
+
+ void setScmTag(String scmTag) {
+ this.scmTag = scmTag;
+ }
+
+ void setMavenHome(File mavenHome) {
+ this.mavenHome = mavenHome;
+ }
+
+ @Override
+ public void execute() throws MojoFailureException, MojoExecutionException {
+ getLog().info("This is a build attestation.");
+ // Build definition
+ BuildDefinition buildDefinition = new BuildDefinition();
+ buildDefinition.setExternalParameters(getExternalParameters());
+ buildDefinition.setResolvedDependencies(getBuildDependencies());
+ // Builder
+ Builder builder = new Builder();
+ // RunDetails
+ RunDetails runDetails = new RunDetails();
+ runDetails.setBuilder(builder);
+ runDetails.setMetadata(getBuildMetadata());
+ // Provenance
+ Provenance provenance = new Provenance();
+ provenance.setBuildDefinition(buildDefinition);
+ provenance.setRunDetails(runDetails);
+ // Statement
+ Statement statement = new Statement();
+ statement.setSubject(getSubjects());
+ statement.setPredicate(provenance);
+
+ writeStatement(statement);
+ }
+
+ private void writeStatement(Statement statement) throws MojoExecutionException {
+ final Path outputPath = outputDirectory.toPath();
+ try {
+ if (!Files.exists(outputPath)) {
+ Files.createDirectories(outputPath);
+ }
+ } catch (IOException e) {
+ throw new MojoExecutionException("Could not create output directory.", e);
+ }
+ final Artifact mainArtifact = project.getArtifact();
+ final Path artifactPath = outputPath.resolve(ArtifactUtils.getFileName(mainArtifact, ATTESTATION_EXTENSION));
+ getLog().info("Writing attestation statement to: " + artifactPath);
+ try (OutputStream os = Files.newOutputStream(artifactPath)) {
+ OBJECT_MAPPER.writerWithDefaultPrettyPrinter().writeValue(os, statement);
+ } catch (IOException e) {
+ throw new MojoExecutionException("Could not write attestation statement to: " + artifactPath, e);
+ }
+ if (!skipAttach) {
+ getLog().info(String.format("Attaching attestation statement as %s-%s.%s", mainArtifact.getArtifactId(), mainArtifact.getVersion(),
+ ATTESTATION_EXTENSION));
+ mavenProjectHelper.attachArtifact(project, ATTESTATION_EXTENSION, null, artifactPath.toFile());
+ }
+ }
+
+ /**
+ * Get the artifacts generated by the build.
+ *
+ * @return A list of resource descriptors for the build artifacts.
+ */
+ private List<ResourceDescriptor> getSubjects() throws MojoExecutionException {
+ List<ResourceDescriptor> subjects = new ArrayList<>();
+ subjects.add(ArtifactUtils.toResourceDescriptor(project.getArtifact()));
+ for (Artifact artifact : project.getAttachedArtifacts()) {
+ subjects.add(ArtifactUtils.toResourceDescriptor(artifact));
+ }
+ return subjects;
+ }
+
+ private Map<String, Object> getExternalParameters() {
+ Map<String, Object> params = new HashMap<>();
+ params.put("jvm.args", ManagementFactory.getRuntimeMXBean().getInputArguments());
+ MavenExecutionRequest request = session.getRequest();
+ params.put("maven.goals", request.getGoals());
+ params.put("maven.profiles", request.getActiveProfiles());
+ params.put("maven.user.properties", request.getUserProperties());
+ params.put("maven.cmdline", getCommandLine(request));
+ Map<String, Object> env = new HashMap<>();
+ params.put("env", env);
+ for (Map.Entry<String, String> entry : System.getenv().entrySet()) {
+ String key = entry.getKey();
+ if ("TZ".equals(key) || "LANG".equals(key) || key.startsWith("LC_")) {
+ env.put(key, entry.getValue());
+ }
+ }
+ return params;
+ }
+
+ private String getCommandLine(MavenExecutionRequest request) {
+ StringBuilder sb = new StringBuilder();
+ for (String goal : request.getGoals()) {
+ sb.append(goal);
+ sb.append(" ");
+ }
+ List<String> activeProfiles = request.getActiveProfiles();
+ if (activeProfiles != null && !activeProfiles.isEmpty()) {
+ sb.append("-P");
+ for (String profile : activeProfiles) {
+ sb.append(profile);
+ sb.append(",");
+ }
+ removeLast(sb);
+ sb.append(" ");
+ }
+ Properties userProperties = request.getUserProperties();
+ for (String propertyName : userProperties.stringPropertyNames()) {
+ sb.append("-D");
+ sb.append(propertyName);
+ sb.append("=");
+ sb.append(userProperties.get(propertyName));
+ sb.append(" ");
+ }
+ removeLast(sb);
+ return sb.toString();
+ }
+
+ private static void removeLast(StringBuilder sb) {
+ if (sb.length() > 0) {
+ sb.setLength(sb.length() - 1);
+ }
+ }
+
+ private List<ResourceDescriptor> getBuildDependencies() throws MojoExecutionException {
+ List<ResourceDescriptor> dependencies = new ArrayList<>();
+ try {
+ dependencies.add(BuildToolDescriptors.jvm(Paths.get(System.getProperty("java.home"))));
+ dependencies.add(BuildToolDescriptors.maven(runtimeInformation.getMavenVersion(), mavenHome.toPath()));
+ dependencies.add(getScmDescriptor());
+ } catch (IOException e) {
+ throw new MojoExecutionException(e);
+ }
+ dependencies.addAll(getProjectDependencies());
+ return dependencies;
+ }
+
+ private List<ResourceDescriptor> getProjectDependencies() throws MojoExecutionException {
+ List<ResourceDescriptor> dependencies = new ArrayList<>();
+ for (Artifact artifact : project.getArtifacts()) {
+ dependencies.add(ArtifactUtils.toResourceDescriptor(artifact));
+ }
+ return dependencies;
+ }
+
+ private ResourceDescriptor getScmDescriptor() throws IOException, MojoExecutionException {
+ ResourceDescriptor scmDescriptor = new ResourceDescriptor();
+ String scmUri = GitUtils.scmToDownloadUri(scmConnectionUrl, scmDirectory.toPath());
+ scmDescriptor.setUri(scmUri);
+ // Compute the revision
+ Map<String, String> digest = new HashMap<>();
+ digest.put("gitCommit", getScmRevision());
+ scmDescriptor.setDigest(digest);
+ return scmDescriptor;
+ }
+
+ private ScmRepository getScmRepository() throws MojoExecutionException {
+ try {
+ return scmManager.makeScmRepository(scmConnectionUrl);
+ } catch (ScmException e) {
+ throw new MojoExecutionException("Failed to create SCM repository", e);
+ }
+ }
+
+ private String getScmRevision() throws MojoExecutionException {
+ ScmRepository scmRepository = getScmRepository();
+ CommandParameters commandParameters = new CommandParameters();
+ try {
+ InfoScmResult result = scmManager.getProviderByRepository(scmRepository).info(scmRepository.getProviderRepository(), new ScmFileSet(scmDirectory)
+ , commandParameters);
+
+ return getScmRevision(result);
+ } catch (ScmException e) {
+ throw new MojoExecutionException("Failed to retrieve SCM revision", e);
+ }
+ }
+
+ private String getScmRevision(InfoScmResult result) throws MojoExecutionException {
+ if (!result.isSuccess()) {
+ throw new MojoExecutionException("Failed to retrieve SCM revision: " + result.getProviderMessage());
+ }
+
+ if (result.getInfoItems() == null || result.getInfoItems().isEmpty()) {
+ throw new MojoExecutionException("No SCM revision information found for " + scmDirectory);
+ }
+
+ InfoItem item = result.getInfoItems().get(0);
+
+ String revision = item.getRevision();
+ if (revision == null) {
+ throw new MojoExecutionException("Empty SCM revision returned for " + scmDirectory);
+ }
+ return revision;
+ }
+
+ private BuildMetadata getBuildMetadata() {
+ OffsetDateTime startedOn = session.getStartTime().toInstant().atOffset(ZoneOffset.UTC);
+ OffsetDateTime finishedOn = OffsetDateTime.now(ZoneOffset.UTC);
+ return new BuildMetadata(session.getRequest().getBuilderId(), startedOn, finishedOn);
+ }
+}
diff --git a/src/main/java/org/apache/commons/build/internal/ArtifactUtils.java b/src/main/java/org/apache/commons/build/internal/ArtifactUtils.java
new file mode 100644
index 0000000..9e1f047
--- /dev/null
+++ b/src/main/java/org/apache/commons/build/internal/ArtifactUtils.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.build.internal;
+
+import org.apache.commons.build.models.slsa.v1_2.ResourceDescriptor;
+import org.apache.commons.codec.digest.DigestUtils;
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.MojoFailureException;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+public final class ArtifactUtils {
+
+ private ArtifactUtils() {
+ // prevent instantiation
+ }
+
+ public static String getFileName(Artifact artifact) {
+ return getFileName(artifact, artifact.getArtifactHandler().getExtension());
+ }
+
+ public static String getFileName(Artifact artifact, String extension) {
+ StringBuilder fileName = new StringBuilder();
+ fileName.append(artifact.getArtifactId()).append("-").append(artifact.getVersion());
+ if (artifact.getClassifier() != null) {
+ fileName.append("-").append(artifact.getClassifier());
+ }
+ fileName.append(".").append(extension);
+ return fileName.toString();
+ }
+
+ public static String getPackageUrl(Artifact artifact) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("pkg:maven/").append(artifact.getGroupId()).append("/").append(artifact.getArtifactId()).append("@").append(artifact.getVersion())
+ .append("?");
+ String classifier = artifact.getClassifier();
+ if (classifier != null) {
+ sb.append("classifier=").append(classifier).append("&");
+ }
+ sb.append("type=").append(artifact.getType());
+ return sb.toString();
+ }
+
+ public static Map<String, String> getChecksums(Artifact artifact) throws IOException {
+ Map<String, String> checksums = new HashMap<>();
+ DigestUtils digest = new DigestUtils(DigestUtils.getSha256Digest());
+ String sha256sum = digest.digestAsHex(artifact.getFile());
+ checksums.put("sha256", sha256sum);
+ return checksums;
+ }
+
+ public static ResourceDescriptor toResourceDescriptor(Artifact artifact) throws MojoExecutionException {
+ ResourceDescriptor descriptor = new ResourceDescriptor();
+ descriptor.setName(getFileName(artifact));
+ descriptor.setUri(getPackageUrl(artifact));
+ if (artifact.getFile() != null) {
+ try {
+ descriptor.setDigest(getChecksums(artifact));
+ } catch (IOException e) {
+ throw new MojoExecutionException("Unable to compute hash for artifact file: " + artifact.getFile(), e);
+ }
+ }
+ return descriptor;
+ }
+}
diff --git a/src/main/java/org/apache/commons/build/internal/BuildToolDescriptors.java b/src/main/java/org/apache/commons/build/internal/BuildToolDescriptors.java
new file mode 100644
index 0000000..0bdc333
--- /dev/null
+++ b/src/main/java/org/apache/commons/build/internal/BuildToolDescriptors.java
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.build.internal;
+
+import org.apache.commons.build.models.slsa.v1_2.ResourceDescriptor;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * Factory methods for {@link ResourceDescriptor} instances representing build-tool dependencies.
+ */
+public final class BuildToolDescriptors {
+
+ private BuildToolDescriptors() {
+ // no instantiation
+ }
+
+ /**
+ * Creates a {@link ResourceDescriptor} for the JDK used during the build.
+ *
+ * @param javaHome path to the JDK home directory (value of the {@code java.home} system property)
+ * @return a descriptor with digest and annotations populated from system properties
+ * @throws IOException if hashing the JDK directory fails
+ */
+ public static ResourceDescriptor jvm(Path javaHome) throws IOException {
+ ResourceDescriptor descriptor = new ResourceDescriptor();
+ descriptor.setName("JDK");
+ Map<String, String> digest = new HashMap<>();
+ digest.put("gitTree", GitUtils.gitTree(javaHome));
+ descriptor.setDigest(digest);
+ String[] propertyNames = {"java.version", "java.vendor", "java.vendor.version", "java.vm.name", "java.vm.version", "java.vm.vendor",
+ "java.runtime.name", "java.runtime.version", "java.specification.version"};
+ Map<String, Object> annotations = new HashMap<>();
+ for (String prop : propertyNames) {
+ annotations.put(prop.substring("java.".length()), System.getProperty(prop));
+ }
+ descriptor.setAnnotations(annotations);
+ return descriptor;
+ }
+
+ /**
+ * Creates a {@link ResourceDescriptor} for the Maven installation used during the build.
+ *
+ * @param version Maven version string
+ * @param mavenHome path to the Maven home directory
+ * @return a descriptor for the Maven installation
+ * @throws IOException if hashing the Maven home directory fails
+ */
+ public static ResourceDescriptor maven(String version, Path mavenHome) throws IOException {
+ ResourceDescriptor descriptor = new ResourceDescriptor();
+ descriptor.setName("Maven");
+ descriptor.setUri("pkg:maven/org.apache.maven/apache-maven@" + version);
+ Map<String, String> digest = new HashMap<>();
+ digest.put("gitTree", GitUtils.gitTree(mavenHome));
+ descriptor.setDigest(digest);
+ Properties buildProps = new Properties();
+ try (InputStream in = BuildToolDescriptors.class.getResourceAsStream("/org/apache/maven/messages/build.properties")) {
+ if (in != null) {
+ buildProps.load(in);
+ }
+ }
+ if (!buildProps.isEmpty()) {
+ Map<String, Object> annotations = new HashMap<>();
+ buildProps.forEach((key, value) -> annotations.put((String) key, value));
+ descriptor.setAnnotations(annotations);
+ }
+ return descriptor;
+ }
+}
diff --git a/src/main/java/org/apache/commons/build/internal/GitUtils.java b/src/main/java/org/apache/commons/build/internal/GitUtils.java
new file mode 100644
index 0000000..01c987b
--- /dev/null
+++ b/src/main/java/org/apache/commons/build/internal/GitUtils.java
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.build.internal;
+
+import org.apache.commons.codec.binary.Hex;
+import org.apache.commons.codec.digest.DigestUtils;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.security.MessageDigest;
+
+public final class GitUtils {
+
+ public static String gitTree(Path path) throws IOException {
+ if (!Files.isDirectory(path)) {
+ throw new IOException("Path is not a directory: " + path);
+ }
+ MessageDigest digest = DigestUtils.getSha1Digest();
+ return Hex.encodeHexString(DigestUtils.gitTree(digest, path));
+ }
+
+ public static String gitBlob(Path path) throws IOException {
+ if (!Files.isRegularFile(path)) {
+ throw new IOException("Path is not a regular file: " + path);
+ }
+ MessageDigest digest = DigestUtils.getSha1Digest();
+ return Hex.encodeHexString(DigestUtils.gitBlob(digest, path));
+ }
+
+ public static String scmToDownloadUri(String scmUri, Path repositoryPath) throws IOException {
+ if (!scmUri.startsWith("scm:git")) {
+ throw new IllegalArgumentException("Invalid scmUri: " + scmUri);
+ }
+ String currentBranch = getCurrentBranch(repositoryPath);
+ return "git+" + scmUri.substring(8) + "@" + currentBranch;
+ }
+
+ public static String getCurrentBranch(Path repositoryPath) throws IOException {
+ Path gitDir = findGitDir(repositoryPath);
+ String head = new String(Files.readAllBytes(gitDir.resolve("HEAD")), StandardCharsets.UTF_8).trim();
+ if (head.startsWith("ref: refs/heads/")) {
+ return head.substring("ref: refs/heads/".length());
+ }
+ // detached HEAD — return the commit SHA
+ return head;
+ }
+
+ private static Path findGitDir(Path path) throws IOException {
+ Path current = path.toAbsolutePath();
+ while (current != null) {
+ Path candidate = current.resolve(".git");
+ if (Files.isDirectory(candidate)) {
+ return candidate;
+ }
+ if (Files.isRegularFile(candidate)) {
+ // git worktree: .git is a file containing "gitdir: /path/to/real/.git"
+ String content = new String(Files.readAllBytes(candidate), StandardCharsets.UTF_8).trim();
+ if (content.startsWith("gitdir: ")) {
+ return Paths.get(content.substring("gitdir: ".length()));
+ }
+ }
+ current = current.getParent();
+ }
+ throw new IOException("No .git directory found above: " + path);
+ }
+
+ private GitUtils() {
+ // no instantiation
+ }
+}
diff --git a/src/main/java/org/apache/commons/build/models/slsa/v1_2/BuildDefinition.java b/src/main/java/org/apache/commons/build/models/slsa/v1_2/BuildDefinition.java
index 12bbab7..df0123e 100644
--- a/src/main/java/org/apache/commons/build/models/slsa/v1_2/BuildDefinition.java
+++ b/src/main/java/org/apache/commons/build/models/slsa/v1_2/BuildDefinition.java
@@ -16,12 +16,13 @@
*/
package org.apache.commons.build.models.slsa.v1_2;
-import com.fasterxml.jackson.annotation.JsonInclude;
-import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
/**
* Inputs that define the build: the build type, external and internal parameters, and resolved dependencies.
*
@@ -30,17 +31,16 @@ import java.util.Objects;
*
* @see <a href="https://slsa.dev/spec/v1.2">SLSA v1.2 Specification</a>
*/
-@JsonInclude(JsonInclude.Include.NON_NULL)
public class BuildDefinition {
@JsonProperty("buildType")
private String buildType = "https://commons.apache.org/builds/0.1.0";
@JsonProperty("externalParameters")
- private Map<String, Object> externalParameters;
+ private Map<String, Object> externalParameters = new HashMap<>();
@JsonProperty("internalParameters")
- private Map<String, Object> internalParameters;
+ private Map<String, Object> internalParameters = new HashMap<>();
@JsonProperty("resolvedDependencies")
private List<ResourceDescriptor> resolvedDependencies;
diff --git a/src/main/java/org/apache/commons/build/models/slsa/v1_2/Builder.java b/src/main/java/org/apache/commons/build/models/slsa/v1_2/Builder.java
index d783592..7466a71 100644
--- a/src/main/java/org/apache/commons/build/models/slsa/v1_2/Builder.java
+++ b/src/main/java/org/apache/commons/build/models/slsa/v1_2/Builder.java
@@ -18,6 +18,8 @@ package org.apache.commons.build.models.slsa.v1_2;
import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -30,13 +32,13 @@ import java.util.Objects;
public class Builder {
@JsonProperty("id")
- private String id;
+ private String id = "https://commons.apache.org/builds/0.1.0";
@JsonProperty("builderDependencies")
- private List<ResourceDescriptor> builderDependencies;
+ private List<ResourceDescriptor> builderDependencies = new ArrayList<>();
@JsonProperty("version")
- private Map<String, String> version;
+ private Map<String, String> version = new HashMap<>();
/** Creates a new Builder instance. */
public Builder() {
diff --git a/src/test/java/org/apache/commons/build/BuildAttestationMojoTest.java b/src/test/java/org/apache/commons/build/BuildAttestationMojoTest.java
new file mode 100644
index 0000000..46ed892
--- /dev/null
+++ b/src/test/java/org/apache/commons/build/BuildAttestationMojoTest.java
@@ -0,0 +1,134 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.build;
+
+import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.io.File;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Date;
+import java.util.Properties;
+
+import org.apache.commons.build.internal.MojoUtils;
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.bridge.MavenRepositorySystem;
+import org.apache.maven.execution.DefaultMavenExecutionRequest;
+import org.apache.maven.execution.DefaultMavenExecutionResult;
+import org.apache.maven.execution.MavenExecutionRequest;
+import org.apache.maven.execution.MavenExecutionResult;
+import org.apache.maven.execution.MavenSession;
+import org.apache.maven.model.Model;
+import org.apache.maven.project.MavenProject;
+import org.apache.maven.project.MavenProjectHelper;
+import org.apache.maven.rtinfo.RuntimeInformation;
+import org.apache.maven.scm.manager.ScmManager;
+import org.codehaus.plexus.PlexusContainer;
+import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
+import org.eclipse.aether.RepositorySystemSession;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+public class BuildAttestationMojoTest {
+
+ @TempDir
+ private static Path localRepositoryPath;
+
+ private static PlexusContainer container;
+ private static RepositorySystemSession repoSession;
+
+ @BeforeAll
+ static void setup() throws Exception {
+ container = MojoUtils.setupContainer();
+ repoSession = MojoUtils.createRepositorySystemSession(container, localRepositoryPath);
+ }
+
+ private static MavenExecutionRequest createMavenExecutionRequest() {
+ DefaultMavenExecutionRequest request = new DefaultMavenExecutionRequest();
+ request.setStartTime(new Date());
+ return request;
+ }
+
+ @SuppressWarnings("deprecation")
+ private static MavenSession createMavenSession(MavenExecutionRequest request, MavenExecutionResult result) {
+ return new MavenSession(container, repoSession, request, result);
+ }
+
+ private static BuildAttestationMojo createBuildAttestationMojo(MavenProject project, MavenProjectHelper projectHelper) throws ComponentLookupException {
+ ScmManager scmManager = container.lookup(ScmManager.class);
+ RuntimeInformation runtimeInfo = container.lookup(RuntimeInformation.class);
+ return new BuildAttestationMojo(project, scmManager, runtimeInfo,
+ createMavenSession(createMavenExecutionRequest(), new DefaultMavenExecutionResult()), projectHelper);
+ }
+
+ private static MavenProject createMavenProject(MavenProjectHelper projectHelper, MavenRepositorySystem repoSystem) throws ComponentLookupException {
+ MavenProject project = new MavenProject(new Model());
+ Artifact artifact = repoSystem.createArtifact("groupId", "artifactId", "1.2.3", null, "jar");
+ project.setArtifact(artifact);
+ project.setGroupId("groupId");
+ project.setArtifactId("artifactId");
+ project.setVersion("1.2.3");
+ // Attach a couple of artifacts
+ projectHelper.attachArtifact(project, "pom", null, new File("src/test/resources/artifacts/artifact-pom.txt"));
+ artifact.setFile(new File("src/test/resources/artifacts/artifact-jar.txt"));
+ return project;
+ }
+
+ @Test
+ void attestationTest() throws Exception {
+ MavenProjectHelper projectHelper = container.lookup(MavenProjectHelper.class);
+ MavenRepositorySystem repoSystem = container.lookup(MavenRepositorySystem.class);
+ MavenProject project = createMavenProject(projectHelper, repoSystem);
+
+ BuildAttestationMojo mojo = createBuildAttestationMojo(project, projectHelper);
+ mojo.setOutputDirectory(new File("target/attestations"));
+ mojo.setScmDirectory(new File("."));
+ mojo.setScmConnectionUrl("scm:git:https://github.com/apache/commons-lang.git");
+ mojo.setScmDeveloperConnectionUrl("scm:git:ssh://git@github.com/apache/commons-lang.git");
+ mojo.setScmTag("tag");
+ mojo.setMavenHome(new File(System.getProperty("maven.home", ".")));
+ mojo.execute();
+
+ Artifact attestation = project.getAttachedArtifacts().stream()
+ .filter(a -> "intoto.json".equals(a.getType()))
+ .findFirst()
+ .orElseThrow(() -> new AssertionError("No intoto.json artifact attached to project"));
+ String json = new String(Files.readAllBytes(attestation.getFile().toPath()), StandardCharsets.UTF_8);
+
+ String resolvedDeps = "predicate.buildDefinition.resolvedDependencies";
+ String javaVersion = System.getProperty("java.version");
+
+ assertThatJson(json)
+ .node(resolvedDeps).isArray()
+ .anySatisfy(dep -> {
+ assertThatJson(dep).node("name").isEqualTo("JDK");
+ assertThatJson(dep).node("annotations.version").isEqualTo(javaVersion);
+ });
+
+ assertThatJson(json)
+ .node(resolvedDeps).isArray()
+ .anySatisfy(dep -> assertThatJson(dep).node("name").isEqualTo("Maven"));
+
+ assertThatJson(json)
+ .node(resolvedDeps).isArray()
+ .anySatisfy(dep -> assertThatJson(dep).node("uri").isString().startsWith("git+https://github.com/apache/commons-lang.git"));
+ }
+}
diff --git a/src/test/java/org/apache/commons/build/internal/MojoUtils.java b/src/test/java/org/apache/commons/build/internal/MojoUtils.java
new file mode 100644
index 0000000..1ac3d25
--- /dev/null
+++ b/src/test/java/org/apache/commons/build/internal/MojoUtils.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.build.internal;
+
+import org.codehaus.plexus.ContainerConfiguration;
+import org.codehaus.plexus.DefaultContainerConfiguration;
+import org.codehaus.plexus.DefaultPlexusContainer;
+import org.codehaus.plexus.PlexusConstants;
+import org.codehaus.plexus.PlexusContainer;
+import org.codehaus.plexus.PlexusContainerException;
+import org.codehaus.plexus.classworlds.ClassWorld;
+import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
+import org.eclipse.aether.DefaultRepositorySystemSession;
+import org.eclipse.aether.RepositoryException;
+import org.eclipse.aether.RepositorySystemSession;
+import org.eclipse.aether.repository.LocalRepository;
+import org.eclipse.aether.repository.LocalRepositoryManager;
+import org.eclipse.aether.repository.RepositoryPolicy;
+import org.eclipse.aether.spi.localrepo.LocalRepositoryManagerFactory;
+
+import java.nio.file.Path;
+
+/**
+ * Utilities to instantiate Mojos in a test environment.
+ */
+public final class MojoUtils {
+
+ private static ContainerConfiguration setupContainerConfiguration() {
+ ClassWorld classWorld =
+ new ClassWorld("plexus.core", Thread.currentThread().getContextClassLoader());
+ return new DefaultContainerConfiguration()
+ .setClassWorld(classWorld)
+ .setClassPathScanning(PlexusConstants.SCANNING_INDEX)
+ .setAutoWiring(true)
+ .setName("maven");
+ }
+
+ public static PlexusContainer setupContainer() throws PlexusContainerException {
+ return new DefaultPlexusContainer(setupContainerConfiguration());
+ }
+
+ public static RepositorySystemSession createRepositorySystemSession(
+ PlexusContainer container, Path localRepositoryPath) throws ComponentLookupException, RepositoryException {
+ LocalRepositoryManagerFactory factory = container.lookup(LocalRepositoryManagerFactory.class, "simple");
+ DefaultRepositorySystemSession repoSession = new DefaultRepositorySystemSession();
+ LocalRepositoryManager manager =
+ factory.newInstance(repoSession, new LocalRepository(localRepositoryPath.toFile()));
+ repoSession.setLocalRepositoryManager(manager);
+ // Default policies
+ repoSession.setUpdatePolicy(RepositoryPolicy.UPDATE_POLICY_DAILY);
+ repoSession.setChecksumPolicy(RepositoryPolicy.CHECKSUM_POLICY_WARN);
+ return repoSession;
+ }
+
+ private MojoUtils() {}
+}
diff --git a/src/test/resources/artifacts/artifact-jar.txt b/src/test/resources/artifacts/artifact-jar.txt
new file mode 100644
index 0000000..103a21e
--- /dev/null
+++ b/src/test/resources/artifacts/artifact-jar.txt
@@ -0,0 +1,2 @@
+// SPDX-License-Identifier: Apache-2.0
+A mock-up of a JAR file
\ No newline at end of file
diff --git a/src/test/resources/artifacts/artifact-pom.txt b/src/test/resources/artifacts/artifact-pom.txt
new file mode 100644
index 0000000..48d1bbc
--- /dev/null
+++ b/src/test/resources/artifacts/artifact-pom.txt
@@ -0,0 +1,2 @@
+// SPDX-License-Identifier: Apache-2.0
+A mock-up of a POM file
\ No newline at end of file
(commons-build-plugin) 01/03: Add SLSA Provenance model classes
Posted by pk...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
pkarwasz pushed a commit to branch feat/slsa
in repository https://gitbox.apache.org/repos/asf/commons-build-plugin.git
commit 7ed5387683793ad89f0a2316262ce25a319c434b
Author: Piotr P. Karwasz <pi...@github.copernik.eu>
AuthorDate: Fri Mar 27 13:08:16 2026 +0100
Add SLSA Provenance model classes
Adds Jackson-annotated classes to generate SLSA Provenance annotations for Commons builds.
---
pom.xml | 12 ++
.../build/models/slsa/v1_2/BuildDefinition.java | 168 ++++++++++++++++
.../build/models/slsa/v1_2/BuildMetadata.java | 136 +++++++++++++
.../commons/build/models/slsa/v1_2/Builder.java | 119 +++++++++++
.../commons/build/models/slsa/v1_2/Provenance.java | 116 +++++++++++
.../build/models/slsa/v1_2/ResourceDescriptor.java | 220 +++++++++++++++++++++
.../commons/build/models/slsa/v1_2/RunDetails.java | 132 +++++++++++++
.../commons/build/models/slsa/v1_2/Statement.java | 126 ++++++++++++
.../build/models/slsa/v1_2/package-info.java | 120 +++++++++++
9 files changed, 1149 insertions(+)
diff --git a/pom.xml b/pom.xml
index 408d67a..408b7df 100644
--- a/pom.xml
+++ b/pom.xml
@@ -87,6 +87,8 @@
<commons.rc.version>RC1</commons.rc.version>
<commons.release.isDistModule>true</commons.release.isDistModule>
<commons.distSvnStagingUrl>scm:svn:https://dist.apache.org/repos/dist/dev/commons/${commons.componentid}</commons.distSvnStagingUrl>
+ <commons.jackson.version>2.21.1</commons.jackson.version>
+ <commons.jackson.annotations.version>2.21</commons.jackson.annotations.version>
<commons.maven.version>3.9.12</commons.maven.version>
<!--
Define the following in ~/.m2/settings.xml in an active profile:
@@ -115,6 +117,16 @@
<artifactId>commons-codec</artifactId>
<version>1.22.0-SNAPSHOT</version>
</dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-databind</artifactId>
+ <version>${commons.jackson.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-annotations</artifactId>
+ <version>${commons.jackson.annotations.version}</version>
+ </dependency>
<dependency>
<!-- Try to deal with https://bz.apache.org/bugzilla/show_bug.cgi?id=66951 -->
<groupId>org.apache.maven</groupId>
diff --git a/src/main/java/org/apache/commons/build/models/slsa/v1_2/BuildDefinition.java b/src/main/java/org/apache/commons/build/models/slsa/v1_2/BuildDefinition.java
new file mode 100644
index 0000000..12bbab7
--- /dev/null
+++ b/src/main/java/org/apache/commons/build/models/slsa/v1_2/BuildDefinition.java
@@ -0,0 +1,168 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.build.models.slsa.v1_2;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Inputs that define the build: the build type, external and internal parameters, and resolved dependencies.
+ *
+ * <p>Specifies everything that influenced the build output. Together with {@link RunDetails}, it forms the complete
+ * {@link Provenance} record.</p>
+ *
+ * @see <a href="https://slsa.dev/spec/v1.2">SLSA v1.2 Specification</a>
+ */
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class BuildDefinition {
+
+ @JsonProperty("buildType")
+ private String buildType = "https://commons.apache.org/builds/0.1.0";
+
+ @JsonProperty("externalParameters")
+ private Map<String, Object> externalParameters;
+
+ @JsonProperty("internalParameters")
+ private Map<String, Object> internalParameters;
+
+ @JsonProperty("resolvedDependencies")
+ private List<ResourceDescriptor> resolvedDependencies;
+
+ /** Creates a new BuildDefinition instance with the default build type. */
+ public BuildDefinition() {}
+
+ /**
+ * Creates a new BuildDefinition with the given build type and external parameters.
+ *
+ * @param buildType URI indicating what type of build was performed
+ * @param externalParameters inputs passed to the build
+ */
+ public BuildDefinition(String buildType, Map<String, Object> externalParameters) {
+ this.buildType = buildType;
+ this.externalParameters = externalParameters;
+ }
+
+ /**
+ * Returns the URI indicating what type of build was performed.
+ *
+ * <p>Determines the meaning of {@code externalParameters} and {@code internalParameters}.</p>
+ *
+ * @return the build type URI
+ */
+ public String getBuildType() {
+ return buildType;
+ }
+
+ /**
+ * Sets the URI indicating what type of build was performed.
+ *
+ * @param buildType the build type URI
+ */
+ public void setBuildType(String buildType) {
+ this.buildType = buildType;
+ }
+
+ /**
+ * Returns the inputs passed to the build, such as command-line arguments or environment variables.
+ *
+ * @return the external parameters map, or {@code null} if not set
+ */
+ public Map<String, Object> getExternalParameters() {
+ return externalParameters;
+ }
+
+ /**
+ * Sets the inputs passed to the build.
+ *
+ * @param externalParameters the external parameters map
+ */
+ public void setExternalParameters(Map<String, Object> externalParameters) {
+ this.externalParameters = externalParameters;
+ }
+
+ /**
+ * Returns the artifacts the build depends on, such as sources, dependencies, build tools, and base images,
+ * specified by URI and digest.
+ *
+ * @return the internal parameters map, or {@code null} if not set
+ */
+ public Map<String, Object> getInternalParameters() {
+ return internalParameters;
+ }
+
+ /**
+ * Sets the artifacts the build depends on.
+ *
+ * @param internalParameters the internal parameters map
+ */
+ public void setInternalParameters(Map<String, Object> internalParameters) {
+ this.internalParameters = internalParameters;
+ }
+
+ /**
+ * Returns the materials that influenced the build.
+ *
+ * <p>Considered incomplete unless resolved materials are present.</p>
+ *
+ * @return the list of resolved dependencies, or {@code null} if not set
+ */
+ public List<ResourceDescriptor> getResolvedDependencies() {
+ return resolvedDependencies;
+ }
+
+ /**
+ * Sets the materials that influenced the build.
+ *
+ * @param resolvedDependencies the list of resolved dependencies
+ */
+ public void setResolvedDependencies(List<ResourceDescriptor> resolvedDependencies) {
+ this.resolvedDependencies = resolvedDependencies;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ BuildDefinition that = (BuildDefinition) o;
+ return Objects.equals(buildType, that.buildType)
+ && Objects.equals(externalParameters, that.externalParameters)
+ && Objects.equals(internalParameters, that.internalParameters)
+ && Objects.equals(resolvedDependencies, that.resolvedDependencies);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(buildType, externalParameters, internalParameters, resolvedDependencies);
+ }
+
+ @Override
+ public String toString() {
+ return "BuildDefinition{"
+ + "buildType='" + buildType + '\''
+ + ", externalParameters=" + externalParameters
+ + ", internalParameters=" + internalParameters
+ + ", resolvedDependencies=" + resolvedDependencies
+ + '}';
+ }
+}
diff --git a/src/main/java/org/apache/commons/build/models/slsa/v1_2/BuildMetadata.java b/src/main/java/org/apache/commons/build/models/slsa/v1_2/BuildMetadata.java
new file mode 100644
index 0000000..5913395
--- /dev/null
+++ b/src/main/java/org/apache/commons/build/models/slsa/v1_2/BuildMetadata.java
@@ -0,0 +1,136 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.build.models.slsa.v1_2;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.time.OffsetDateTime;
+import java.util.Objects;
+
+/**
+ * Metadata about a build invocation: its identifier and start and finish timestamps.
+ *
+ * @see <a href="https://slsa.dev/spec/v1.2">SLSA v1.2 Specification</a>
+ */
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class BuildMetadata {
+
+ @JsonProperty("invocationId")
+ private String invocationId;
+
+ @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'")
+ @JsonProperty("startedOn")
+ private OffsetDateTime startedOn;
+
+ @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'")
+ @JsonProperty("finishedOn")
+ private OffsetDateTime finishedOn;
+
+ /** Creates a new BuildMetadata instance. */
+ public BuildMetadata() {
+ }
+
+ /**
+ * Creates a new BuildMetadata instance with all fields set.
+ *
+ * @param invocationId identifier for this build invocation
+ * @param startedOn timestamp when the build started
+ * @param finishedOn timestamp when the build completed
+ */
+ public BuildMetadata(String invocationId, OffsetDateTime startedOn, OffsetDateTime finishedOn) {
+ this.invocationId = invocationId;
+ this.startedOn = startedOn;
+ this.finishedOn = finishedOn;
+ }
+
+ /**
+ * Returns the identifier for this build invocation.
+ *
+ * <p>Useful for finding associated logs or other ad-hoc analysis. The exact meaning and format is defined by the
+ * builder and is treated as opaque and case-sensitive. The value SHOULD be globally unique.</p>
+ *
+ * @return the invocation identifier, or {@code null} if not set
+ */
+ public String getInvocationId() {
+ return invocationId;
+ }
+
+ /**
+ * Sets the identifier for this build invocation.
+ *
+ * @param invocationId the invocation identifier
+ */
+ public void setInvocationId(String invocationId) {
+ this.invocationId = invocationId;
+ }
+
+ /**
+ * Returns the timestamp of when the build started, serialized as RFC 3339 in UTC ({@code "Z"} suffix).
+ *
+ * @return the start timestamp, or {@code null} if not set
+ */
+ public OffsetDateTime getStartedOn() {
+ return startedOn;
+ }
+
+ /**
+ * Sets the timestamp of when the build started.
+ *
+ * @param startedOn the start timestamp
+ */
+ public void setStartedOn(OffsetDateTime startedOn) {
+ this.startedOn = startedOn;
+ }
+
+ /**
+ * Returns the timestamp of when the build completed, serialized as RFC 3339 in UTC ({@code "Z"} suffix).
+ *
+ * @return the completion timestamp, or {@code null} if not set
+ */
+ public OffsetDateTime getFinishedOn() {
+ return finishedOn;
+ }
+
+ /**
+ * Sets the timestamp of when the build completed.
+ *
+ * @param finishedOn the completion timestamp
+ */
+ public void setFinishedOn(OffsetDateTime finishedOn) {
+ this.finishedOn = finishedOn;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof BuildMetadata))
+ return false;
+ BuildMetadata that = (BuildMetadata) o;
+ return Objects.equals(invocationId, that.invocationId) && Objects.equals(startedOn, that.startedOn) && Objects.equals(finishedOn, that.finishedOn);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(invocationId, startedOn, finishedOn);
+ }
+
+ @Override
+ public String toString() {
+ return "BuildMetadata{invocationId='" + invocationId + "', startedOn=" + startedOn + ", finishedOn=" + finishedOn + '}';
+ }
+}
diff --git a/src/main/java/org/apache/commons/build/models/slsa/v1_2/Builder.java b/src/main/java/org/apache/commons/build/models/slsa/v1_2/Builder.java
new file mode 100644
index 0000000..d783592
--- /dev/null
+++ b/src/main/java/org/apache/commons/build/models/slsa/v1_2/Builder.java
@@ -0,0 +1,119 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.build.models.slsa.v1_2;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Entity that executed the build and is trusted to have correctly performed the operation and populated the provenance.
+ *
+ * @see <a href="https://slsa.dev/spec/v1.2">SLSA v1.2 Specification</a>
+ */
+public class Builder {
+
+ @JsonProperty("id")
+ private String id;
+
+ @JsonProperty("builderDependencies")
+ private List<ResourceDescriptor> builderDependencies;
+
+ @JsonProperty("version")
+ private Map<String, String> version;
+
+ /** Creates a new Builder instance. */
+ public Builder() {
+ }
+
+ /**
+ * Returns the identifier of the builder.
+ *
+ * @return the builder identifier URI
+ */
+ public String getId() {
+ return id;
+ }
+
+ /**
+ * Sets the identifier of the builder.
+ *
+ * @param id the builder identifier URI
+ */
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ /**
+ * Returns orchestrator dependencies that do not run within the build workload and do not affect the build output,
+ * but may affect provenance generation or security guarantees.
+ *
+ * @return the list of builder dependencies, or {@code null} if not set
+ */
+ public List<ResourceDescriptor> getBuilderDependencies() {
+ return builderDependencies;
+ }
+
+ /**
+ * Sets the orchestrator dependencies that may affect provenance generation or security guarantees.
+ *
+ * @param builderDependencies the list of builder dependencies
+ */
+ public void setBuilderDependencies(List<ResourceDescriptor> builderDependencies) {
+ this.builderDependencies = builderDependencies;
+ }
+
+ /**
+ * Returns a map of build platform component names to their versions.
+ *
+ * @return the version map, or {@code null} if not set
+ */
+ public Map<String, String> getVersion() {
+ return version;
+ }
+
+ /**
+ * Sets the map of build platform component names to their versions.
+ *
+ * @param version the version map
+ */
+ public void setVersion(Map<String, String> version) {
+ this.version = version;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof Builder))
+ return false;
+ Builder that = (Builder) o;
+ return Objects.equals(id, that.id)
+ && Objects.equals(builderDependencies, that.builderDependencies)
+ && Objects.equals(version, that.version);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(id, builderDependencies, version);
+ }
+
+ @Override
+ public String toString() {
+ return "Builder{id='" + id + "', builderDependencies=" + builderDependencies + ", version=" + version + '}';
+ }
+}
diff --git a/src/main/java/org/apache/commons/build/models/slsa/v1_2/Provenance.java b/src/main/java/org/apache/commons/build/models/slsa/v1_2/Provenance.java
new file mode 100644
index 0000000..91d1d18
--- /dev/null
+++ b/src/main/java/org/apache/commons/build/models/slsa/v1_2/Provenance.java
@@ -0,0 +1,116 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.build.models.slsa.v1_2;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.Objects;
+
+/**
+ * Root predicate of an SLSA v1.2 provenance attestation, describing what was built and how.
+ *
+ * <p>Combines a {@link BuildDefinition} (the inputs) with {@link RunDetails} (the execution context). Intended to be
+ * used as the {@code predicate} field of an in-toto {@link Statement}.</p>
+ *
+ * @see <a href="https://slsa.dev/spec/v1.2">SLSA v1.2 Specification</a>
+ */
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class Provenance {
+
+ /** Predicate type URI used in the in-toto {@link Statement} wrapping this provenance. */
+ public static final String PREDICATE_TYPE = "https://slsa.dev/provenance/v1";
+
+ @JsonProperty("buildDefinition")
+ private BuildDefinition buildDefinition;
+
+ @JsonProperty("runDetails")
+ private RunDetails runDetails;
+
+ /** Creates a new Provenance instance. */
+ public Provenance() {}
+
+ /**
+ * Creates a new Provenance with the given build definition and run details.
+ *
+ * @param buildDefinition inputs that defined the build
+ * @param runDetails details about the build invocation
+ */
+ public Provenance(BuildDefinition buildDefinition, RunDetails runDetails) {
+ this.buildDefinition = buildDefinition;
+ this.runDetails = runDetails;
+ }
+
+ /**
+ * Returns the build definition describing all inputs that produced the build output.
+ *
+ * <p>Includes source code, dependencies, build tools, base images, and other materials.</p>
+ *
+ * @return the build definition, or {@code null} if not set
+ */
+ public BuildDefinition getBuildDefinition() {
+ return buildDefinition;
+ }
+
+ /**
+ * Sets the build definition describing all inputs that produced the build output.
+ *
+ * @param buildDefinition the build definition
+ */
+ public void setBuildDefinition(BuildDefinition buildDefinition) {
+ this.buildDefinition = buildDefinition;
+ }
+
+ /**
+ * Returns the details about the invocation of the build tool and the environment in which it was run.
+ *
+ * @return the run details, or {@code null} if not set
+ */
+ public RunDetails getRunDetails() {
+ return runDetails;
+ }
+
+ /**
+ * Sets the details about the invocation of the build tool and the environment in which it was run.
+ *
+ * @param runDetails the run details
+ */
+ public void setRunDetails(RunDetails runDetails) {
+ this.runDetails = runDetails;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ Provenance that = (Provenance) o;
+ return Objects.equals(buildDefinition, that.buildDefinition) && Objects.equals(runDetails, that.runDetails);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(buildDefinition, runDetails);
+ }
+
+ @Override
+ public String toString() {
+ return "Provenance{buildDefinition=" + buildDefinition + ", runDetails=" + runDetails + '}';
+ }
+}
diff --git a/src/main/java/org/apache/commons/build/models/slsa/v1_2/ResourceDescriptor.java b/src/main/java/org/apache/commons/build/models/slsa/v1_2/ResourceDescriptor.java
new file mode 100644
index 0000000..a34d4da
--- /dev/null
+++ b/src/main/java/org/apache/commons/build/models/slsa/v1_2/ResourceDescriptor.java
@@ -0,0 +1,220 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.build.models.slsa.v1_2;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Description of an artifact or resource referenced in the build, identified by URI and cryptographic digest.
+ *
+ * <p>Used to represent inputs to, outputs from, or byproducts of the build process.</p>
+ *
+ * @see <a href="https://slsa.dev/spec/v1.2">SLSA v1.2 Specification</a>
+ */
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class ResourceDescriptor {
+
+ @JsonProperty("name")
+ private String name;
+
+ @JsonProperty("uri")
+ private String uri;
+
+ @JsonProperty("digest")
+ private Map<String, String> digest;
+
+ @JsonProperty("content")
+ private byte[] content;
+
+ @JsonProperty("downloadLocation")
+ private String downloadLocation;
+
+ @JsonProperty("mediaType")
+ private String mediaType;
+
+ @JsonProperty("annotations")
+ private Map<String, Object> annotations;
+
+ /** Creates a new ResourceDescriptor instance. */
+ public ResourceDescriptor() {
+ }
+
+ /**
+ * Creates a new ResourceDescriptor with the given URI and digest.
+ *
+ * @param uri URI identifying the resource
+ * @param digest map of digest algorithm names to their hex-encoded values
+ */
+ public ResourceDescriptor(String uri, Map<String, String> digest) {
+ this.uri = uri;
+ this.digest = digest;
+ }
+
+ /**
+ * Returns the name of the resource.
+ *
+ * @return the resource name, or {@code null} if not set
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Sets the name of the resource.
+ *
+ * @param name the resource name
+ */
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ /**
+ * Returns the URI identifying the resource.
+ *
+ * @return the resource URI, or {@code null} if not set
+ */
+ public String getUri() {
+ return uri;
+ }
+
+ /**
+ * Sets the URI identifying the resource.
+ *
+ * @param uri the resource URI
+ */
+ public void setUri(String uri) {
+ this.uri = uri;
+ }
+
+ /**
+ * Returns the map of cryptographic digest algorithms to their corresponding hex-encoded values for this resource.
+ *
+ * <p>Common keys include {@code "sha256"} and {@code "sha512"}.</p>
+ *
+ * @return the digest map, or {@code null} if not set
+ */
+ public Map<String, String> getDigest() {
+ return digest;
+ }
+
+ /**
+ * Sets the map of cryptographic digest algorithms to their hex-encoded values.
+ *
+ * @param digest the digest map
+ */
+ public void setDigest(Map<String, String> digest) {
+ this.digest = digest;
+ }
+
+ /**
+ * Returns the raw contents of the resource, base64-encoded when serialized to JSON.
+ *
+ * @return the resource content, or {@code null} if not set
+ */
+ public byte[] getContent() {
+ return content;
+ }
+
+ /**
+ * Sets the raw contents of the resource.
+ *
+ * @param content the resource content
+ */
+ public void setContent(byte[] content) {
+ this.content = content;
+ }
+
+ /**
+ * Returns the download URI for the resource, if different from {@link #getUri()}.
+ *
+ * @return the download location URI, or {@code null} if not set
+ */
+ public String getDownloadLocation() {
+ return downloadLocation;
+ }
+
+ /**
+ * Sets the download URI for the resource.
+ *
+ * @param downloadLocation the download location URI
+ */
+ public void setDownloadLocation(String downloadLocation) {
+ this.downloadLocation = downloadLocation;
+ }
+
+ /**
+ * Returns the media type of the resource (e.g., {@code "application/octet-stream"}).
+ *
+ * @return the media type, or {@code null} if not set
+ */
+ public String getMediaType() {
+ return mediaType;
+ }
+
+ /**
+ * Sets the media type of the resource.
+ *
+ * @param mediaType the media type
+ */
+ public void setMediaType(String mediaType) {
+ this.mediaType = mediaType;
+ }
+
+ /**
+ * Returns additional key-value metadata about the resource, such as filename, size, or builder-specific attributes.
+ *
+ * @return the annotations map, or {@code null} if not set
+ */
+ public Map<String, Object> getAnnotations() {
+ return annotations;
+ }
+
+ /**
+ * Sets additional key-value metadata about the resource.
+ *
+ * @param annotations the annotations map
+ */
+ public void setAnnotations(Map<String, Object> annotations) {
+ this.annotations = annotations;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ ResourceDescriptor that = (ResourceDescriptor) o;
+ return Objects.equals(uri, that.uri) && Objects.equals(digest, that.digest);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(uri, digest);
+ }
+
+ @Override
+ public String toString() {
+ return "ResourceDescriptor{uri='" + uri + '\'' + ", digest=" + digest + '}';
+ }
+}
diff --git a/src/main/java/org/apache/commons/build/models/slsa/v1_2/RunDetails.java b/src/main/java/org/apache/commons/build/models/slsa/v1_2/RunDetails.java
new file mode 100644
index 0000000..92872c9
--- /dev/null
+++ b/src/main/java/org/apache/commons/build/models/slsa/v1_2/RunDetails.java
@@ -0,0 +1,132 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.build.models.slsa.v1_2;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Details about the build invocation: the builder identity, execution metadata, and any byproduct artifacts.
+ *
+ * @see <a href="https://slsa.dev/spec/v1.2">SLSA v1.2 Specification</a>
+ */
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class RunDetails {
+
+ @JsonProperty("builder")
+ private Builder builder;
+
+ @JsonProperty("metadata")
+ private BuildMetadata metadata;
+
+ @JsonProperty("byproducts")
+ private List<ResourceDescriptor> byproducts;
+
+ /** Creates a new RunDetails instance. */
+ public RunDetails() {}
+
+ /**
+ * Creates a new RunDetails with the given builder and metadata.
+ *
+ * @param builder entity that executed the build
+ * @param metadata metadata about the build invocation
+ */
+ public RunDetails(Builder builder, BuildMetadata metadata) {
+ this.builder = builder;
+ this.metadata = metadata;
+ }
+
+ /**
+ * Returns the builder that executed the invocation.
+ *
+ * <p>Trusted to have correctly performed the operation and populated this provenance.</p>
+ *
+ * @return the builder, or {@code null} if not set
+ */
+ public Builder getBuilder() {
+ return builder;
+ }
+
+ /**
+ * Sets the builder that executed the invocation.
+ *
+ * @param builder the builder
+ */
+ public void setBuilder(Builder builder) {
+ this.builder = builder;
+ }
+
+ /**
+ * Returns the metadata about the build invocation, including its identifier and timing.
+ *
+ * @return the build metadata, or {@code null} if not set
+ */
+ public BuildMetadata getMetadata() {
+ return metadata;
+ }
+
+ /**
+ * Sets the metadata about the build invocation.
+ *
+ * @param metadata the build metadata
+ */
+ public void setMetadata(BuildMetadata metadata) {
+ this.metadata = metadata;
+ }
+
+ /**
+ * Returns artifacts produced as a side effect of the build that are not the primary output.
+ *
+ * @return the list of byproduct artifacts, or {@code null} if not set
+ */
+ public List<ResourceDescriptor> getByproducts() {
+ return byproducts;
+ }
+
+ /**
+ * Sets the artifacts produced as a side effect of the build that are not the primary output.
+ *
+ * @param byproducts the list of byproduct artifacts
+ */
+ public void setByproducts(List<ResourceDescriptor> byproducts) {
+ this.byproducts = byproducts;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ RunDetails that = (RunDetails) o;
+ return Objects.equals(builder, that.builder) && Objects.equals(metadata, that.metadata) && Objects.equals(byproducts, that.byproducts);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(builder, metadata, byproducts);
+ }
+
+ @Override
+ public String toString() {
+ return "RunDetails{builder=" + builder + ", metadata=" + metadata + ", byproducts=" + byproducts + '}';
+ }
+}
diff --git a/src/main/java/org/apache/commons/build/models/slsa/v1_2/Statement.java b/src/main/java/org/apache/commons/build/models/slsa/v1_2/Statement.java
new file mode 100644
index 0000000..a6d690c
--- /dev/null
+++ b/src/main/java/org/apache/commons/build/models/slsa/v1_2/Statement.java
@@ -0,0 +1,126 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.build.models.slsa.v1_2;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * In-toto v1 attestation envelope that binds a set of subject artifacts to an SLSA provenance predicate.
+ *
+ * @see <a href="https://github.com/in-toto/attestation/blob/main/spec/v1/statement.md">in-toto Statement v1</a>
+ */
+public class Statement {
+
+ @JsonProperty("_type")
+ private final String _type = "https://in-toto.io/Statement/v1";
+
+ @JsonProperty("subject")
+ private List<ResourceDescriptor> subject;
+
+ @JsonProperty("predicateType")
+ private String predicateType;
+
+ @JsonProperty("predicate")
+ private Provenance predicate;
+
+ /** Creates a new Statement instance. */
+ public Statement() {
+ }
+
+ /**
+ * Returns the schema identifier for this statement.
+ *
+ * @return the fixed type URI {@code "https://in-toto.io/Statement/v1"}
+ */
+ public String get_type() {
+ return _type;
+ }
+
+ /**
+ * Returns the set of software artifacts that the attestation applies to.
+ *
+ * <p>Each element represents a single artifact. Artifacts are matched purely by digest, regardless of content
+ * type.</p>
+ *
+ * @return the list of subject artifacts, or {@code null} if not set
+ */
+ public List<ResourceDescriptor> getSubject() {
+ return subject;
+ }
+
+ /**
+ * Sets the set of software artifacts that the attestation applies to.
+ *
+ * @param subject the list of subject artifacts
+ */
+ public void setSubject(List<ResourceDescriptor> subject) {
+ this.subject = subject;
+ }
+
+ /**
+ * Returns the URI identifying the type of the predicate.
+ *
+ * @return the predicate type URI, or {@code null} if no predicate has been set
+ */
+ public String getPredicateType() {
+ return predicateType;
+ }
+
+ /**
+ * Returns the provenance predicate.
+ *
+ * <p>Unset is treated the same as set-but-empty. May be omitted if {@code predicateType} fully describes the
+ * predicate.</p>
+ *
+ * @return the provenance predicate, or {@code null} if not set
+ */
+ public Provenance getPredicate() {
+ return predicate;
+ }
+
+ /**
+ * Sets the provenance predicate and automatically assigns {@code predicateType} to the SLSA provenance v1 URI.
+ *
+ * @param predicate the provenance predicate
+ */
+ public void setPredicate(Provenance predicate) {
+ this.predicate = predicate;
+ this.predicateType = Provenance.PREDICATE_TYPE;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof Statement))
+ return false;
+ Statement statement = (Statement) o;
+ return Objects.equals(subject, statement.subject) && Objects.equals(predicateType, statement.predicateType) && Objects.equals(predicate,
+ statement.predicate);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(subject, predicateType, predicate);
+ }
+
+ @Override
+ public String toString() {
+ return "Statement{_type='" + _type + "', subject=" + subject + ", predicateType='" + predicateType + "', predicate=" + predicate + '}';
+ }
+}
diff --git a/src/main/java/org/apache/commons/build/models/slsa/v1_2/package-info.java b/src/main/java/org/apache/commons/build/models/slsa/v1_2/package-info.java
new file mode 100644
index 0000000..eb77ffe
--- /dev/null
+++ b/src/main/java/org/apache/commons/build/models/slsa/v1_2/package-info.java
@@ -0,0 +1,120 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * SLSA 1.2 Build Attestation Models.
+ *
+ * <p>This package provides Jackson-annotated model classes that implement the <a
+ * href="https://slsa.dev/spec/v1.2">Supply-chain Levels for Software Artifacts (SLSA) v1.2
+ * specification</a>.
+ *
+ * <h2>Overview</h2>
+ *
+ * <p>SLSA is a framework for evaluating and improving the security posture of build systems. SLSA
+ * v1.2 defines a standard for recording build provenance - information about how software
+ * artifacts were produced.
+ *
+ * <h2>Core Models</h2>
+ *
+ * <ul>
+ * <li><b>{@link org.apache.commons.build.models.slsa.v1_2.Provenance}</b> - Root object
+ * describing the build provenance. Contains BuildDefinition and RunDetails.
+ * <li><b>{@link org.apache.commons.build.models.slsa.v1_2.BuildDefinition}</b> - Specifies
+ * the inputs that define the build, including build type, configuration, external
+ * parameters, and resolved dependencies.
+ * <li><b>{@link org.apache.commons.build.models.slsa.v1_2.RunDetails}</b> - Specifies the
+ * details about the build invocation and environment, including the builder identity and
+ * build metadata.
+ * </ul>
+ *
+ * <h2>Supporting Models</h2>
+ *
+ * <ul>
+ * <li><b>{@link org.apache.commons.build.models.slsa.v1_2.Builder}</b> - Identifies the
+ * entity that executed the build.
+ * <li><b>{@link org.apache.commons.build.models.slsa.v1_2.BuildMetadata}</b> - Contains
+ * metadata about the build invocation, including timing information.
+ * <li><b>{@link org.apache.commons.build.models.slsa.v1_2.ResourceDescriptor}</b> - Describes
+ * an artifact or resource referenced in the build by URI and cryptographic digest.
+ * </ul>
+ *
+ * <h2>Usage Example</h2>
+ *
+ * <pre>
+ * // Create a builder
+ * Builder builder = new Builder();
+ * builder.setId("https://github.com/actions");
+ * builder.setVersion("1.0");
+ *
+ * // Create build metadata
+ * BuildMetadata buildMetadata = new BuildMetadata();
+ * buildMetadata.setInvocationId("build-12345");
+ * buildMetadata.setStartedOn(OffsetDateTime.now(ZoneOffset.UTC));
+ * buildMetadata.setFinishedOn(OffsetDateTime.now(ZoneOffset.UTC));
+ *
+ * // Create run details
+ * RunDetails runDetails = new RunDetails();
+ * runDetails.setBuilder(builder);
+ * runDetails.setMetadata(buildMetadata);
+ *
+ * // Create build definition
+ * BuildDefinition buildDefinition = new BuildDefinition();
+ * buildDefinition.setBuildType("https://github.com/actions");
+ * buildDefinition.setExternalParameters(new HashMap<>());
+ *
+ * // Create provenance
+ * Provenance provenance = new Provenance();
+ * provenance.setBuildDefinition(buildDefinition);
+ * provenance.setRunDetails(runDetails);
+ *
+ * // Serialize with Jackson
+ * ObjectMapper mapper = new ObjectMapper();
+ * String json = mapper.writeValueAsString(provenance);
+ * </pre>
+ *
+ * <h2>Jackson Integration</h2>
+ *
+ * <p>All models use Jackson annotations for JSON serialization/deserialization:
+ *
+ * <ul>
+ * <li>{@code @JsonProperty} - Maps field names to JSON properties
+ * <li>{@code @JsonInclude} - Controls inclusion of null/empty values in serialization
+ * <li>{@code @JsonFormat} - Specifies date/time formatting
+ * </ul>
+ *
+ * <p>The models are compatible with Jackson's ObjectMapper and support both serialization to
+ * JSON and deserialization from JSON.
+ *
+ * <h2>Validation</h2>
+ *
+ * <p>Some models include Jakarta Validation annotations:
+ *
+ * <ul>
+ * <li>{@code @NotBlank} - Ensures required string fields are not empty
+ * </ul>
+ *
+ * <p>Users can enable validation using a Jakarta Validation provider to ensure provenance
+ * integrity.
+ *
+ * <h2>Reference</h2>
+ *
+ * @see <a href="https://slsa.dev/spec/v1.2">SLSA v1.2 Specification</a>
+ * @see <a href="https://github.com/in-toto/attestation">In-toto Attestation Framework</a>
+ * @see <a href="https://github.com/FasterXML/jackson">Jackson JSON processor</a>
+ */
+package org.apache.commons.build.models.slsa.v1_2;
+
(commons-build-plugin) 03/03: Document Commons `buildType`
Posted by pk...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
pkarwasz pushed a commit to branch feat/slsa
in repository https://gitbox.apache.org/repos/asf/commons-build-plugin.git
commit 8ac265dd97b8090e3ab4c969e02e2a58382e6cb2
Author: Piotr P. Karwasz <pi...@github.copernik.eu>
AuthorDate: Sun Mar 29 20:19:28 2026 +0200
Document Commons `buildType`
---
src/site/markdown/slsa/v0.1.0.md | 21 +++++++++++++++++++++
1 file changed, 21 insertions(+)
diff --git a/src/site/markdown/slsa/v0.1.0.md b/src/site/markdown/slsa/v0.1.0.md
new file mode 100644
index 0000000..2462f6a
--- /dev/null
+++ b/src/site/markdown/slsa/v0.1.0.md
@@ -0,0 +1,21 @@
+<!-- SPDX-License-Identifier: Apache-2.0 -->
+# SLSA Provenance Build Type
+
+This is a [SLSA Build Provenance](https://slsa.dev/spec/v1.2/build-provenance) build type to describe releases produced
+by Apache Commons PMC release managers on their own equipment.
+
+## Build definition
+
+Artifacts are generated by a single Maven execution, usually of the form.
+
+```shell
+mvn -Prelease deploy
+```
+
+### External parameters
+
+### Internal parameters
+
+### Resolved dependencies
+
+The build depends on two main tools installed on the release manager machine: JDK and
\ No newline at end of file