Skip to content

Commit 52c7a19

Browse files
committed
Use HTTP server for TUF conformance testing
Signed-off-by: Aaron Lew <[email protected]>
1 parent cd936f9 commit 52c7a19

File tree

5 files changed

+209
-5
lines changed

5 files changed

+209
-5
lines changed

.github/workflows/tuf-conformance.yml

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
name: TUF Conformance Tests
2+
permissions:
3+
contents: read
24

35
on:
46
push:
@@ -37,11 +39,14 @@ jobs:
3739
- name: Setup Gradle
3840
uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
3941

40-
- name: Build tuf cli
41-
run: ./gradlew :tuf-cli:build
42+
- name: Build tuf cli and server jar
43+
run: ./gradlew :tuf-cli:serverShadowJar
4244

43-
- name: Unpack tuf distribution
44-
run: tar -xvf ${{ github.workspace }}/tuf-cli/build/distributions/tuf-cli-*.tar --strip-components 1
45+
- name: Start test server in background
46+
run: java -jar ${{ github.workspace }}/tuf-cli/build/libs/tuf-cli-server-all.jar &
47+
48+
- name: Wait for server to be ready
49+
run: curl --retry-connrefused --retry 10 --retry-delay 1 --fail http://localhost:8080/
4550

4651
- name: Set up JDK ${{ matrix.java-version }}
4752
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
@@ -51,5 +56,5 @@ jobs:
5156

5257
- uses: theupdateframework/tuf-conformance@9bfc222a371e30ad5511eb17449f68f855fb9d8f # v2.3.0
5358
with:
54-
entrypoint: ${{ github.workspace }}/bin/tuf-cli
59+
entrypoint: ${{ github.workspace }}/tuf-cli/tuf-cli-server
5560
artifact-name: test repositories for tuf-cli java ${{ matrix.java-version }}

tuf-cli/build.gradle.kts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1+
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
2+
13
plugins {
24
id("build-logic.java")
35
id("application")
6+
id("com.gradleup.shadow") version "9.0.0-rc3"
47
}
58

69
repositories {
@@ -15,6 +18,11 @@ dependencies {
1518
implementation(platform("com.google.oauth-client:google-oauth-client-bom:1.39.0"))
1619
implementation("com.google.oauth-client:google-oauth-client")
1720

21+
implementation("org.eclipse.jetty:jetty-server:11.0.24")
22+
implementation("org.eclipse.jetty:jetty-servlet:11.0.24")
23+
24+
implementation("org.slf4j:slf4j-simple:2.0.17")
25+
1826
annotationProcessor("info.picocli:picocli-codegen:4.7.6")
1927
}
2028

@@ -37,3 +45,20 @@ distributions.main {
3745
tasks.run.configure {
3846
workingDir = rootProject.projectDir
3947
}
48+
49+
tasks.register<ShadowJar>("serverShadowJar") {
50+
archiveBaseName.set("tuf-cli-server")
51+
archiveClassifier.set("all")
52+
archiveVersion.set("")
53+
54+
mergeServiceFiles()
55+
56+
from(sourceSets.main.get().output)
57+
configurations = listOf(project.configurations.runtimeClasspath.get())
58+
59+
exclude("META-INF/*.SF", "META-INF/*.DSA", "META-INF/*.RSA")
60+
61+
manifest {
62+
attributes("Main-Class" to "dev.sigstore.tuf.cli.TufConformanceServer")
63+
}
64+
}
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
/*
2+
* Copyright 2025 The Sigstore Authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package dev.sigstore.tuf.cli;
17+
18+
import com.google.gson.Gson;
19+
import jakarta.servlet.ServletException;
20+
import jakarta.servlet.http.HttpServletRequest;
21+
import jakarta.servlet.http.HttpServletResponse;
22+
import java.io.ByteArrayOutputStream;
23+
import java.io.IOException;
24+
import java.io.InputStream;
25+
import java.io.OutputStream;
26+
import java.io.PrintStream;
27+
import java.nio.charset.StandardCharsets;
28+
import java.util.Arrays;
29+
import java.util.List;
30+
import java.util.Map;
31+
import org.eclipse.jetty.server.Request;
32+
import org.eclipse.jetty.server.Server;
33+
import org.eclipse.jetty.server.handler.AbstractHandler;
34+
35+
public class TufConformanceServer {
36+
37+
private static final Gson GSON = new Gson();
38+
39+
private static class ExecuteRequest {
40+
String cwd;
41+
String[] args;
42+
}
43+
44+
public static void main(String[] args) throws Exception {
45+
int port = 8080;
46+
Server server = new Server(port);
47+
server.setHandler(new TufConformanceHandler());
48+
server.start();
49+
server.join();
50+
}
51+
52+
public static class TufConformanceHandler extends AbstractHandler {
53+
@Override
54+
public void handle(
55+
String target,
56+
Request baseRequest,
57+
HttpServletRequest request,
58+
HttpServletResponse response)
59+
throws IOException, ServletException {
60+
if ("/".equals(target)) {
61+
handleHealthCheck(response);
62+
baseRequest.setHandled(true);
63+
} else if ("/execute".equals(target) && "POST".equals(request.getMethod())) {
64+
handleExecute(request, response);
65+
baseRequest.setHandled(true);
66+
}
67+
}
68+
}
69+
70+
private static void handleExecute(HttpServletRequest request, HttpServletResponse response)
71+
throws IOException {
72+
ExecuteRequest executeRequest;
73+
try (InputStream is = request.getInputStream()) {
74+
String requestBody = new String(is.readAllBytes(), StandardCharsets.UTF_8);
75+
executeRequest = GSON.fromJson(requestBody, ExecuteRequest.class);
76+
}
77+
78+
// Tests should not be run in parallel, to ensure orderly input/output
79+
PrintStream originalOut = System.out;
80+
PrintStream originalErr = System.err;
81+
82+
ByteArrayOutputStream outContent = new ByteArrayOutputStream();
83+
ByteArrayOutputStream errContent = new ByteArrayOutputStream();
84+
85+
try (PrintStream outPs = new PrintStream(outContent, true, StandardCharsets.UTF_8);
86+
PrintStream errPs = new PrintStream(errContent, true, StandardCharsets.UTF_8)) {
87+
System.setOut(outPs);
88+
System.setErr(errPs);
89+
90+
List<String> resolvedArgs = new java.util.ArrayList<>();
91+
resolvedArgs.addAll(Arrays.asList(executeRequest.args));
92+
93+
int exitCode =
94+
new picocli.CommandLine(new Tuf()).execute(resolvedArgs.toArray(new String[0]));
95+
96+
Map<String, Object> responseMap =
97+
Map.of(
98+
"stdout", outContent.toString(StandardCharsets.UTF_8),
99+
"stderr", errContent.toString(StandardCharsets.UTF_8),
100+
"exitCode", exitCode);
101+
String jsonResponse = GSON.toJson(responseMap);
102+
103+
response.setStatus(HttpServletResponse.SC_OK);
104+
response.setContentType("application/json");
105+
byte[] responseBytes = jsonResponse.getBytes(StandardCharsets.UTF_8);
106+
response.setContentLength(responseBytes.length);
107+
108+
try (OutputStream os = response.getOutputStream()) {
109+
os.write(responseBytes);
110+
}
111+
} finally {
112+
System.setOut(originalOut);
113+
System.setErr(originalErr);
114+
}
115+
}
116+
117+
private static void handleHealthCheck(HttpServletResponse response) throws IOException {
118+
response.setStatus(HttpServletResponse.SC_OK);
119+
response.getWriter().println("OK");
120+
}
121+
}

tuf-cli/tuf-cli-server

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#!/bin/bash
2+
3+
set -o pipefail -o errexit -o nounset
4+
5+
CWD=$PWD
6+
7+
ARGS_JSON="["
8+
for arg in "$@"; do
9+
escaped_arg=$(echo -n "$arg" | jq -R -s '.')
10+
ARGS_JSON="$ARGS_JSON$escaped_arg,"
11+
done
12+
if [[ $ARGS_JSON == *, ]]; then
13+
ARGS_JSON="${ARGS_JSON%,}"
14+
fi
15+
ARGS_JSON="$ARGS_JSON]"
16+
17+
JSON_PAYLOAD=$(jq -nc --arg cwd "$CWD" --argjson args "$ARGS_JSON" '{"cwd": $cwd, "args": $args}')
18+
19+
RESPONSE=$(curl -s -X POST --header "Content-Type: application/json" --data-binary "$JSON_PAYLOAD" http://localhost:8080/execute)
20+
21+
STDOUT=$(echo "$RESPONSE" | jq -r .stdout)
22+
STDERR=$(echo "$RESPONSE" | jq -r .stderr)
23+
EXIT_CODE=$(echo "$RESPONSE" | jq .exitCode)
24+
25+
echo -n "$STDOUT"
26+
echo -n "$STDERR" >&2
27+
28+
exit "$EXIT_CODE"

tuf-cli/tuf-cli-server.xfails

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
test_metadata_bytes_match
2+
test_unusual_role_name[?]
3+
test_unusual_role_name[#]
4+
test_unusual_role_name[/delegatedrole]
5+
test_unusual_role_name[../delegatedrole]
6+
test_snapshot_rollback[basic]
7+
test_snapshot_rollback[with
8+
test_static_repository[tuf-on-ci-0.11]
9+
test_graph_traversal[basic-delegation]
10+
test_graph_traversal[single-level-delegations]
11+
test_graph_traversal[two-level-delegations]
12+
test_graph_traversal[two-level-test-DFS-order-of-traversal]
13+
test_graph_traversal[three-level-delegation-test-DFS-order-of-traversal]
14+
test_graph_traversal[two-level-terminating-ignores-all-but-roles-descendants]
15+
test_graph_traversal[three-level-terminating-ignores-all-but-roles-descendants]
16+
test_graph_traversal[two-level-ignores-all-branches-not-matching-paths]
17+
test_graph_traversal[three-level-ignores-all-branches-not-matching-paths]
18+
test_graph_traversal[cyclic-graph]
19+
test_graph_traversal[two-roles-delegating-to-a-third]
20+
test_graph_traversal[two-roles-delegating-to-a-third-different-paths]
21+
test_targetfile_search[targetpath matches wildcard]
22+
test_targetfile_search[targetpath with separators x]
23+
test_targetfile_search[targetpath with separators y]
24+
test_targetfile_search[targetpath is not delegated by all roles in the chain]
25+
test_snapshot_rollback[with hashes]

0 commit comments

Comments
 (0)