-
Notifications
You must be signed in to change notification settings - Fork 0
test(supply-chain): rebase Maven checksum RED on live develop #281
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from all commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
39fecd9
test(supply-chain): require Maven distribution checksum
seonghobae ff937c3
fix(supply-chain): verify Maven distribution checksum
seonghobae 726a4c0
test(supply-chain): require wrapper preflight in CI
seonghobae b41792e
fix(supply-chain): preflight Maven wrapper in workflows
seonghobae 0fd506c
merge: refresh Maven checksum lane from live develop
seonghobae b4ab6af
test(supply-chain): detect Windows Maven Wrapper invocations
seonghobae 48141b2
Merge branch 'develop' into security/maven-wrapper-checksum-106add
opencode-agent[bot] a038a3b
Merge branch 'develop' into security/maven-wrapper-checksum-106add
opencode-agent[bot] 706c600
Merge branch 'develop' into security/maven-wrapper-checksum-106add
opencode-agent[bot] 4f62bc0
Merge branch 'develop' into security/maven-wrapper-checksum-106add
opencode-agent[bot] 33c7f42
Merge branch 'develop' into security/maven-wrapper-checksum-106add
opencode-agent[bot] edc800c
Merge branch 'develop' into security/maven-wrapper-checksum-106add
opencode-agent[bot] a4faee0
Merge branch 'develop' into security/maven-wrapper-checksum-106add
github-actions[bot] 2664071
Merge branch 'develop' into security/maven-wrapper-checksum-106add
opencode-agent[bot] File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| import java.io.IOException; | ||
| import java.io.Reader; | ||
| import java.nio.charset.StandardCharsets; | ||
| import java.nio.file.Files; | ||
| import java.nio.file.Path; | ||
| import java.util.Properties; | ||
|
|
||
| /** | ||
| * Fail-closed Maven Wrapper bootstrap preflight for GitHub Actions. | ||
| * | ||
| * <p>This source-file-mode utility runs on the JDK configured by the workflow before any | ||
| * {@code mvnw} or {@code mvnw.cmd} invocation. It verifies that the Maven distribution remains | ||
| * bound to the exact reviewed URL and SHA-256 checksum, so deleting or changing the checksum | ||
| * cannot silently bypass integrity verification before Maven starts.</p> | ||
| */ | ||
| public final class VerifyMavenWrapperIntegrity { | ||
|
|
||
| private static final String REVIEWED_WRAPPER_VERSION = "3.3.4"; | ||
| private static final String REVIEWED_DISTRIBUTION_TYPE = "only-script"; | ||
| private static final String REVIEWED_DISTRIBUTION_URL = | ||
| "https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.11/" | ||
| + "apache-maven-3.9.11-bin.zip"; | ||
| private static final String REVIEWED_DISTRIBUTION_SHA256 = | ||
| "0d7125e8c91097b36edb990ea5934e6c68b4440eef4ea96510a0f6815e7eeadb"; | ||
|
|
||
| private VerifyMavenWrapperIntegrity() { | ||
| } | ||
|
|
||
| /** | ||
| * Validates the reviewed Maven Wrapper bootstrap inputs before Maven can execute. | ||
| * | ||
| * @param args ignored command-line arguments | ||
| * @throws IOException when the wrapper properties cannot be read | ||
| */ | ||
| public static void main(String[] args) throws IOException { | ||
| Path propertiesPath = Path.of(".mvn", "wrapper", "maven-wrapper.properties"); | ||
| if (!Files.isRegularFile(propertiesPath)) { | ||
| throw new IllegalStateException("Missing Maven Wrapper properties: " + propertiesPath); | ||
| } | ||
|
|
||
| Properties properties = new Properties(); | ||
| try (Reader reader = Files.newBufferedReader(propertiesPath, StandardCharsets.UTF_8)) { | ||
| properties.load(reader); | ||
| } | ||
|
|
||
| requireExact(properties, "wrapperVersion", REVIEWED_WRAPPER_VERSION); | ||
| requireExact(properties, "distributionType", REVIEWED_DISTRIBUTION_TYPE); | ||
| requireExact(properties, "distributionUrl", REVIEWED_DISTRIBUTION_URL); | ||
| requireExact(properties, "distributionSha256Sum", REVIEWED_DISTRIBUTION_SHA256); | ||
| System.out.println("Maven Wrapper integrity preflight passed."); | ||
| } | ||
|
|
||
| private static void requireExact(Properties properties, String key, String expected) { | ||
| String actual = properties.getProperty(key); | ||
| if (!expected.equals(actual)) { | ||
| throw new IllegalStateException( | ||
| "Maven Wrapper integrity preflight rejected " + key | ||
| + ": expected reviewed value but found " + String.valueOf(actual) | ||
| ); | ||
| } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,4 @@ | ||
| wrapperVersion=3.3.4 | ||
| distributionType=only-script | ||
| distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.11/apache-maven-3.9.11-bin.zip | ||
| distributionSha256Sum=0d7125e8c91097b36edb990ea5934e6c68b4440eef4ea96510a0f6815e7eeadb |
183 changes: 183 additions & 0 deletions
183
etl-service/src/test/java/com/xtrmetl/etl/documentation/MavenWrapperIntegrityTest.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,183 @@ | ||
| package com.xtrmetl.etl.documentation; | ||
|
|
||
| import org.junit.jupiter.api.Test; | ||
|
|
||
| import java.io.IOException; | ||
| import java.io.Reader; | ||
| import java.nio.charset.StandardCharsets; | ||
| import java.nio.file.Files; | ||
| import java.nio.file.Path; | ||
| import java.nio.file.Paths; | ||
| import java.util.List; | ||
| import java.util.Properties; | ||
|
|
||
| import static org.junit.jupiter.api.Assertions.assertEquals; | ||
| import static org.junit.jupiter.api.Assertions.assertNotNull; | ||
| import static org.junit.jupiter.api.Assertions.assertTrue; | ||
|
|
||
| /** | ||
| * Prevents Maven Wrapper bootstrap from executing an unverified Maven distribution. | ||
| * | ||
| * <p>The wrapper downloads Maven before project compilation and tests can run. This repository | ||
| * therefore treats the Maven distribution URL and its reviewed SHA-256 as one atomic build-input | ||
| * contract. A Maven version or URL change must carry a newly reviewed checksum in the same change; | ||
| * deleting the checksum must fail deterministically without network access.</p> | ||
| */ | ||
| class MavenWrapperIntegrityTest { | ||
|
|
||
| private static final String REVIEWED_DISTRIBUTION_URL = | ||
| "https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.11/" | ||
| + "apache-maven-3.9.11-bin.zip"; | ||
| private static final String REVIEWED_DISTRIBUTION_SHA256 = | ||
| "0d7125e8c91097b36edb990ea5934e6c68b4440eef4ea96510a0f6815e7eeadb"; | ||
| private static final String PREFLIGHT_COMMAND = | ||
| "run: java .github/scripts/VerifyMavenWrapperIntegrity.java"; | ||
|
|
||
| /** | ||
| * Requires the fixed Maven 3.9.11 download to remain bound to its reviewed SHA-256 checksum. | ||
| * | ||
| * @throws IOException when the wrapper properties cannot be read as repository source | ||
| */ | ||
| @Test | ||
| void bindsMavenDistributionUrlToReviewedSha256() throws IOException { | ||
| Properties properties = new Properties(); | ||
| Path wrapperProperties = projectRoot().resolve( | ||
| ".mvn/wrapper/maven-wrapper.properties" | ||
| ); | ||
| assertTrue(Files.isRegularFile(wrapperProperties), "Maven Wrapper properties must exist"); | ||
|
|
||
| try (Reader reader = Files.newBufferedReader(wrapperProperties, StandardCharsets.UTF_8)) { | ||
| properties.load(reader); | ||
| } | ||
|
|
||
| assertEquals("3.3.4", properties.getProperty("wrapperVersion")); | ||
| assertEquals("only-script", properties.getProperty("distributionType")); | ||
| assertEquals( | ||
| REVIEWED_DISTRIBUTION_URL, | ||
| properties.getProperty("distributionUrl"), | ||
| "Changing the Maven distribution requires review of a matching checksum" | ||
| ); | ||
|
|
||
| String distributionSha256 = properties.getProperty("distributionSha256Sum"); | ||
| assertNotNull( | ||
| distributionSha256, | ||
| "Maven Wrapper must verify the downloaded Maven distribution with SHA-256" | ||
| ); | ||
| assertTrue( | ||
| distributionSha256.matches("[0-9a-f]{64}"), | ||
| "distributionSha256Sum must be 64 lowercase hexadecimal characters" | ||
| ); | ||
| assertEquals( | ||
| REVIEWED_DISTRIBUTION_SHA256, | ||
| distributionSha256, | ||
| "The checksum must match the reviewed Maven 3.9.11 distribution" | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * Requires a fail-closed integrity preflight immediately before every CI/SBOM wrapper step. | ||
| * | ||
| * <p>The preflight must have the same GitHub Actions {@code if:} condition as the wrapper step, | ||
| * so neither Unix nor Windows execution can bootstrap Maven without validating the reviewed | ||
| * distribution URL and checksum first.</p> | ||
| * | ||
| * @throws IOException when a workflow cannot be read as repository source | ||
| */ | ||
| @Test | ||
| void preflightsEveryCiAndSbomWrapperInvocation() throws IOException { | ||
| for (String workflow : List.of( | ||
| ".github/workflows/ci.yml", | ||
| ".github/workflows/sbom.yml" | ||
| )) { | ||
| List<String> lines = Files.readAllLines( | ||
| projectRoot().resolve(workflow), | ||
| StandardCharsets.UTF_8 | ||
| ); | ||
| int wrapperInvocations = 0; | ||
|
|
||
| for (int lineIndex = 0; lineIndex < lines.size(); lineIndex++) { | ||
| String line = lines.get(lineIndex).trim(); | ||
| if (!line.startsWith("run:") || !containsWrapperInvocation(line)) { | ||
| continue; | ||
| } | ||
| wrapperInvocations++; | ||
|
|
||
| int wrapperStep = previousStepStart(lines, lineIndex); | ||
| int preflightStep = previousStepStart(lines, wrapperStep - 1); | ||
| assertTrue( | ||
| preflightStep >= 0, | ||
| workflow + ": wrapper invocation must have a preceding preflight step" | ||
| ); | ||
|
|
||
| String preflightBlock = String.join( | ||
| "\n", | ||
| lines.subList(preflightStep, wrapperStep) | ||
| ); | ||
| assertTrue( | ||
| preflightBlock.contains(PREFLIGHT_COMMAND), | ||
| workflow + ": every Maven Wrapper invocation must be immediately preceded " | ||
| + "by the integrity preflight" | ||
| ); | ||
|
|
||
| String wrapperCondition = stepCondition(lines, wrapperStep, lineIndex + 1); | ||
| String preflightCondition = stepCondition(lines, preflightStep, wrapperStep); | ||
| assertEquals( | ||
| wrapperCondition, | ||
| preflightCondition, | ||
| workflow + ": preflight and wrapper step must use the same condition" | ||
| ); | ||
| } | ||
|
|
||
| assertTrue( | ||
| wrapperInvocations > 0, | ||
| workflow + ": expected at least one Maven Wrapper invocation" | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| private static boolean containsWrapperInvocation(String line) { | ||
| return line.contains("./mvnw ") || line.contains(".\\mvnw.cmd "); | ||
| } | ||
|
seonghobae marked this conversation as resolved.
|
||
|
|
||
| private static int previousStepStart(List<String> lines, int fromIndex) { | ||
| for (int lineIndex = fromIndex; lineIndex >= 0; lineIndex--) { | ||
| if (lines.get(lineIndex).trim().startsWith("- name:")) { | ||
| return lineIndex; | ||
| } | ||
| } | ||
| return -1; | ||
| } | ||
|
|
||
| private static String stepCondition(List<String> lines, int startInclusive, int endExclusive) { | ||
| for (int lineIndex = startInclusive; lineIndex < endExclusive; lineIndex++) { | ||
| String line = lines.get(lineIndex).trim(); | ||
| if (line.startsWith("if:")) { | ||
| return line; | ||
| } | ||
| } | ||
| return ""; | ||
| } | ||
|
|
||
| /** | ||
| * Finds the repository root from reactor-root or module-local Maven execution. | ||
| * | ||
| * @return absolute repository root containing the wrapper configuration | ||
| */ | ||
| private static Path projectRoot() { | ||
| Path current = Paths.get(System.getProperty("user.dir")).toAbsolutePath(); | ||
| Path lastPomParent = null; | ||
| while (current != null) { | ||
| if (Files.exists(current.resolve(".git"))) { | ||
| return current; | ||
| } | ||
| if (Files.exists(current.resolve("pom.xml"))) { | ||
| lastPomParent = current; | ||
| } | ||
| current = current.getParent(); | ||
| } | ||
| if (lastPomParent != null) { | ||
| return lastPomParent; | ||
| } | ||
| throw new IllegalStateException("Could not find project root"); | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.