Skip to content

Commit 1074cbc

Browse files
committed
Add build-attestation target
This PR was moved from apache/commons-build-plugin#417 It adds a goal to generate 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
1 parent fb16018 commit 1074cbc

20 files changed

Lines changed: 2154 additions & 1 deletion

checkstyle.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@
185185
<module name="UpperEll" />
186186
<module name="ImportOrder">
187187
<property name="option" value="top"/>
188-
<property name="groups" value="java,javax,org"/>
188+
<property name="groups" value="java,javax"/>
189189
<property name="ordered" value="true"/>
190190
<property name="separated" value="true"/>
191191
</module>

fb-excludes.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@
1818
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
1919
xsi:schemaLocation="https://github.com/spotbugs/filter/3.0.0 https://raw.githubusercontent.com/spotbugs/spotbugs/3.1.0/spotbugs/etc/findbugsfilter.xsd">
2020

21+
<!-- Mutable objects are not passed to untrusted methods, so we exclude these checks -->
22+
<Match>
23+
<Bug pattern="EI_EXPOSE_REP,EI_EXPOSE_REP2" />
24+
</Match>
25+
2126
<!-- Omit junit tests -->
2227
<Match>
2328
<Class name="~.*\.*Test.*"/>

pom.xml

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,22 @@
113113
<!-- Until Maven plugins used here don't fail the Moditect plugin -->
114114
<moditect.skip>true</moditect.skip>
115115
<japicmp.skip>true</japicmp.skip>
116+
<!-- Dependency versions -->
117+
<commons.jackson.version>2.21.1</commons.jackson.version>
118+
<commons.jackson.annotations.version>2.21</commons.jackson.annotations.version>
119+
<commons.slf4j.version>2.0.17</commons.slf4j.version>
116120
</properties>
121+
<dependencyManagement>
122+
<dependencies>
123+
<dependency>
124+
<groupId>org.slf4j</groupId>
125+
<artifactId>slf4j-bom</artifactId>
126+
<version>${commons.slf4j.version}</version>
127+
<type>pom</type>
128+
<scope>import</scope>
129+
</dependency>
130+
</dependencies>
131+
</dependencyManagement>
117132
<dependencies>
118133
<dependency>
119134
<groupId>org.apache.commons</groupId>
@@ -151,6 +166,18 @@
151166
<artifactId>maven-scm-api</artifactId>
152167
<version>${maven-scm.version}</version>
153168
</dependency>
169+
<dependency>
170+
<groupId>org.apache.maven.scm</groupId>
171+
<artifactId>maven-scm-manager-plexus</artifactId>
172+
<version>${maven-scm.version}</version>
173+
<scope>compile</scope>
174+
</dependency>
175+
<dependency>
176+
<groupId>org.apache.maven.scm</groupId>
177+
<artifactId>maven-scm-provider-gitexe</artifactId>
178+
<version>${maven-scm.version}</version>
179+
<scope>runtime</scope>
180+
</dependency>
154181
<dependency>
155182
<groupId>org.apache.maven.scm</groupId>
156183
<artifactId>maven-scm-provider-svnexe</artifactId>
@@ -171,6 +198,22 @@
171198
<artifactId>commons-compress</artifactId>
172199
<version>1.28.0</version>
173200
</dependency>
201+
<dependency>
202+
<groupId>com.fasterxml.jackson.core</groupId>
203+
<artifactId>jackson-databind</artifactId>
204+
<version>${commons.jackson.version}</version>
205+
</dependency>
206+
<dependency>
207+
<groupId>com.fasterxml.jackson.core</groupId>
208+
<artifactId>jackson-annotations</artifactId>
209+
<version>${commons.jackson.annotations.version}</version>
210+
</dependency>
211+
<dependency>
212+
<groupId>com.fasterxml.jackson.datatype</groupId>
213+
<artifactId>jackson-datatype-jsr310</artifactId>
214+
<version>${commons.jackson.version}</version>
215+
<scope>runtime</scope>
216+
</dependency>
174217
<dependency>
175218
<groupId>org.apache.maven.plugin-testing</groupId>
176219
<artifactId>maven-plugin-testing-harness</artifactId>
@@ -188,11 +231,28 @@
188231
<artifactId>junit-jupiter</artifactId>
189232
<scope>test</scope>
190233
</dependency>
234+
<dependency>
235+
<groupId>net.javacrumbs.json-unit</groupId>
236+
<artifactId>json-unit-assertj</artifactId>
237+
<version>2.40.1</version>
238+
<scope>test</scope>
239+
</dependency>
240+
<dependency>
241+
<groupId>org.junit.jupiter</groupId>
242+
<artifactId>junit-jupiter-api</artifactId>
243+
<scope>test</scope>
244+
</dependency>
191245
<dependency>
192246
<groupId>org.junit.vintage</groupId>
193247
<artifactId>junit-vintage-engine</artifactId>
194248
<scope>test</scope>
195249
</dependency>
250+
<dependency>
251+
<groupId>org.mockito</groupId>
252+
<artifactId>mockito-core</artifactId>
253+
<version>4.11.0</version>
254+
<scope>test</scope>
255+
</dependency>
196256
<!-- A bit of jar-hell requires this to come last. -->
197257
<dependency>
198258
<groupId>org.apache.maven</groupId>
@@ -223,6 +283,11 @@
223283
</exclusion>
224284
</exclusions>
225285
</dependency>
286+
<dependency>
287+
<groupId>org.slf4j</groupId>
288+
<artifactId>slf4j-simple</artifactId>
289+
<scope>test</scope>
290+
</dependency>
226291
</dependencies>
227292
<build>
228293
<defaultGoal>clean verify apache-rat:check checkstyle:check spotbugs:check javadoc:javadoc site</defaultGoal>
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* https://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.commons.release.plugin.internal;
18+
19+
import java.io.IOException;
20+
import java.util.HashMap;
21+
import java.util.Map;
22+
23+
import org.apache.commons.codec.digest.DigestUtils;
24+
import org.apache.commons.release.plugin.slsa.v1_2.ResourceDescriptor;
25+
import org.apache.maven.artifact.Artifact;
26+
import org.apache.maven.plugin.MojoExecutionException;
27+
28+
/**
29+
* Utilities to convert {@link Artifact} from and to other types.
30+
*/
31+
public final class ArtifactUtils {
32+
33+
/** No instances. */
34+
private ArtifactUtils() {
35+
// prevent instantiation
36+
}
37+
38+
/**
39+
* Returns the conventional filename for the given artifact.
40+
*
41+
* @param artifact A Maven artifact.
42+
* @return A filename.
43+
*/
44+
public static String getFileName(Artifact artifact) {
45+
return getFileName(artifact, artifact.getArtifactHandler().getExtension());
46+
}
47+
48+
/**
49+
* Returns the filename for the given artifact with a changed extension.
50+
*
51+
* @param artifact A Maven artifact.
52+
* @param extension The file name extension.
53+
* @return A filename.
54+
*/
55+
public static String getFileName(Artifact artifact, String extension) {
56+
StringBuilder fileName = new StringBuilder();
57+
fileName.append(artifact.getArtifactId()).append("-").append(artifact.getVersion());
58+
if (artifact.getClassifier() != null) {
59+
fileName.append("-").append(artifact.getClassifier());
60+
}
61+
fileName.append(".").append(extension);
62+
return fileName.toString();
63+
}
64+
65+
/**
66+
* Returns the Package URL corresponding to this artifact.
67+
*
68+
* @param artifact A maven artifact.
69+
* @return A PURL for the given artifact.
70+
*/
71+
public static String getPackageUrl(Artifact artifact) {
72+
StringBuilder sb = new StringBuilder();
73+
sb.append("pkg:maven/").append(artifact.getGroupId()).append("/").append(artifact.getArtifactId()).append("@").append(artifact.getVersion())
74+
.append("?");
75+
String classifier = artifact.getClassifier();
76+
if (classifier != null) {
77+
sb.append("classifier=").append(classifier).append("&");
78+
}
79+
sb.append("type=").append(artifact.getType());
80+
return sb.toString();
81+
}
82+
83+
/**
84+
* Returns a map of checksum algorithm names to hex-encoded digest values for the given artifact file.
85+
*
86+
* @param artifact A Maven artifact.
87+
* @return A map of checksum algorithm names to hex-encoded digest values.
88+
* @throws IOException If an I/O error occurs reading the artifact file.
89+
*/
90+
private static Map<String, String> getChecksums(Artifact artifact) throws IOException {
91+
Map<String, String> checksums = new HashMap<>();
92+
DigestUtils digest = new DigestUtils(DigestUtils.getSha256Digest());
93+
String sha256sum = digest.digestAsHex(artifact.getFile());
94+
checksums.put("sha256", sha256sum);
95+
return checksums;
96+
}
97+
98+
/**
99+
* Converts a Maven artifact to a SLSA {@link ResourceDescriptor}.
100+
*
101+
* @param artifact A Maven artifact.
102+
* @return A SLSA resource descriptor.
103+
* @throws MojoExecutionException If an I/O error occurs retrieving the artifact.
104+
*/
105+
public static ResourceDescriptor toResourceDescriptor(Artifact artifact) throws MojoExecutionException {
106+
ResourceDescriptor descriptor = new ResourceDescriptor();
107+
descriptor.setName(getFileName(artifact));
108+
descriptor.setUri(getPackageUrl(artifact));
109+
if (artifact.getFile() != null) {
110+
try {
111+
descriptor.setDigest(getChecksums(artifact));
112+
} catch (IOException e) {
113+
throw new MojoExecutionException("Unable to compute hash for artifact file: " + artifact.getFile(), e);
114+
}
115+
}
116+
return descriptor;
117+
}
118+
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* https://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.commons.release.plugin.internal;
18+
19+
import java.io.IOException;
20+
import java.io.InputStream;
21+
import java.nio.file.Path;
22+
import java.util.HashMap;
23+
import java.util.Map;
24+
import java.util.Properties;
25+
26+
import org.apache.commons.release.plugin.slsa.v1_2.ResourceDescriptor;
27+
28+
/**
29+
* Factory methods for {@link ResourceDescriptor} instances representing build-tool dependencies.
30+
*/
31+
public final class BuildToolDescriptors {
32+
33+
/** No instances. */
34+
private BuildToolDescriptors() {
35+
// no instantiation
36+
}
37+
38+
/**
39+
* Creates a {@link ResourceDescriptor} for the JDK used during the build.
40+
*
41+
* @param javaHome path to the JDK home directory (value of the {@code java.home} system property)
42+
* @return a descriptor with digest and annotations populated from system properties
43+
* @throws IOException if hashing the JDK directory fails
44+
*/
45+
public static ResourceDescriptor jvm(Path javaHome) throws IOException {
46+
ResourceDescriptor descriptor = new ResourceDescriptor();
47+
descriptor.setName("JDK");
48+
Map<String, String> digest = new HashMap<>();
49+
digest.put("gitTree", GitUtils.gitTree(javaHome));
50+
descriptor.setDigest(digest);
51+
String[] propertyNames = {"java.version", "java.vendor", "java.vendor.version", "java.vm.name", "java.vm.version", "java.vm.vendor",
52+
"java.runtime.name", "java.runtime.version", "java.specification.version"};
53+
Map<String, Object> annotations = new HashMap<>();
54+
for (String prop : propertyNames) {
55+
annotations.put(prop.substring("java.".length()), System.getProperty(prop));
56+
}
57+
descriptor.setAnnotations(annotations);
58+
return descriptor;
59+
}
60+
61+
/**
62+
* Creates a {@link ResourceDescriptor} for the Maven installation used during the build.
63+
*
64+
* @param version Maven version string
65+
* @param mavenHome path to the Maven home directory
66+
* @return a descriptor for the Maven installation
67+
* @throws IOException if hashing the Maven home directory fails
68+
*/
69+
public static ResourceDescriptor maven(String version, Path mavenHome) throws IOException {
70+
ResourceDescriptor descriptor = new ResourceDescriptor();
71+
descriptor.setName("Maven");
72+
descriptor.setUri("pkg:maven/org.apache.maven/apache-maven@" + version);
73+
Map<String, String> digest = new HashMap<>();
74+
digest.put("gitTree", GitUtils.gitTree(mavenHome));
75+
descriptor.setDigest(digest);
76+
Properties buildProps = new Properties();
77+
try (InputStream in = BuildToolDescriptors.class.getResourceAsStream("/org/apache/maven/messages/build.properties")) {
78+
if (in != null) {
79+
buildProps.load(in);
80+
}
81+
}
82+
if (!buildProps.isEmpty()) {
83+
Map<String, Object> annotations = new HashMap<>();
84+
buildProps.forEach((key, value) -> annotations.put((String) key, value));
85+
descriptor.setAnnotations(annotations);
86+
}
87+
return descriptor;
88+
}
89+
}

0 commit comments

Comments
 (0)