diff --git a/.replit b/.replit index d50c78fd..12fbdc2c 100644 --- a/.replit +++ b/.replit @@ -3,6 +3,8 @@ modules = ["java", "java-17"] [nix] channel = "stable-24_05" +# Zipkin is intentionally not bootstrapped by Replit. Use the repository's Docker Compose +# topology for the supported, reviewable observability runtime. [workflows] runButton = "Project" @@ -23,18 +25,6 @@ args = "Java Version Check" task = "workflow.run" args = "Build Project" -[[workflows.workflow.tasks]] -task = "workflow.run" -args = "Run Zipkin Server" - -[[workflows.workflow.tasks]] -task = "workflow.run" -args = "Run Zipkin" - -[[workflows.workflow.tasks]] -task = "workflow.run" -args = "Run Eureka Server" - [[workflows.workflow.tasks]] task = "workflow.run" args = "Run All Tests" @@ -43,10 +33,6 @@ args = "Run All Tests" task = "workflow.run" args = "Check Test Status" -[[workflows.workflow.tasks]] -task = "workflow.run" -args = "Build and Run Microservices" - [[workflows.workflow]] name = "Run Microservices" author = "agent" @@ -56,8 +42,8 @@ agentRequireRestartOnSave = false [[workflows.workflow.tasks]] task = "shell.exec" -args = "mvn clean install && java -jar eureka-server/target/eureka-server-1.0-SNAPSHOT.jar & sleep 30 && java -jar config-server/target/config-server-1.0-SNAPSHOT.jar & sleep 30 && java -jar zipkin.jar & sleep 30 && java -jar etl-service/target/etl-service-1.0-SNAPSHOT.jar & java -jar cdc-service/target/cdc-service-1.0-SNAPSHOT.jar & java -jar zuul-gateway/target/zuul-gateway-1.0-SNAPSHOT.jar" -waitForPort = 9411 +args = "mvn clean install && java -jar eureka-server/target/eureka-server-1.0-SNAPSHOT.jar & sleep 30 && java -jar config-server/target/config-server-1.0-SNAPSHOT.jar & sleep 30 && java -jar etl-service/target/etl-service-1.0-SNAPSHOT.jar & java -jar cdc-service/target/cdc-service-1.0-SNAPSHOT.jar & java -jar zuul-gateway/target/zuul-gateway-1.0-SNAPSHOT.jar" +waitForPort = 8761 [[workflows.workflow]] name = "Java Version Check" @@ -81,30 +67,6 @@ agentRequireRestartOnSave = false task = "shell.exec" args = "mvn clean install" -[[workflows.workflow]] -name = "Run Zipkin Server" -author = "agent" - -[workflows.workflow.metadata] -agentRequireRestartOnSave = false - -[[workflows.workflow.tasks]] -task = "shell.exec" -args = "curl -sSL https://zipkin.io/quickstart.sh | bash -s && java -jar zipkin.jar" -waitForPort = 9411 - -[[workflows.workflow]] -name = "Run Zipkin" -author = "agent" - -[workflows.workflow.metadata] -agentRequireRestartOnSave = false - -[[workflows.workflow.tasks]] -task = "shell.exec" -args = "java -jar zipkin.jar --server.port=9412" -waitForPort = 9412 - [[workflows.workflow]] name = "Run Eureka Server" author = "agent" @@ -152,16 +114,8 @@ args = "mvn clean install && java -jar eureka-server/target/eureka-server-1.0-SN waitForPort = 8761 [deployment] -run = ["sh", "-c", "mvn clean install && java -jar eureka-server/target/eureka-server-1.0-SNAPSHOT.jar & sleep 30 && java -jar config-server/target/config-server-1.0-SNAPSHOT.jar & sleep 30 && java -jar zipkin.jar & sleep 30 && java -jar etl-service/target/etl-service-1.0-SNAPSHOT.jar & java -jar cdc-service/target/cdc-service-1.0-SNAPSHOT.jar & java -jar zuul-gateway/target/zuul-gateway-1.0-SNAPSHOT.jar"] +run = ["sh", "-c", "mvn clean install && java -jar eureka-server/target/eureka-server-1.0-SNAPSHOT.jar & sleep 30 && java -jar config-server/target/config-server-1.0-SNAPSHOT.jar & sleep 30 && java -jar etl-service/target/etl-service-1.0-SNAPSHOT.jar & java -jar cdc-service/target/cdc-service-1.0-SNAPSHOT.jar & java -jar zuul-gateway/target/zuul-gateway-1.0-SNAPSHOT.jar"] [[ports]] localPort = 8761 externalPort = 3001 - -[[ports]] -localPort = 9411 -externalPort = 80 - -[[ports]] -localPort = 9412 -externalPort = 3000 diff --git a/etl-service/src/test/java/com/xtrmetl/etl/security/RepositoryRuntimeSupplyChainTest.java b/etl-service/src/test/java/com/xtrmetl/etl/security/RepositoryRuntimeSupplyChainTest.java new file mode 100644 index 00000000..5cad7938 --- /dev/null +++ b/etl-service/src/test/java/com/xtrmetl/etl/security/RepositoryRuntimeSupplyChainTest.java @@ -0,0 +1,99 @@ +package com.xtrmetl.etl.security; + +import org.junit.jupiter.api.Test; + +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.util.regex.Pattern; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Guards repository-owned developer launch configuration against unsafe or conflicting + * developer-runtime authority paths. + */ +class RepositoryRuntimeSupplyChainTest { + + private static final Path PROJECT_ROOT = projectRoot(); + private static final Pattern REMOTE_SHELL_PIPE = Pattern.compile( + "(?im)\\bcurl\\b[^\\r\\n|]*\\|\\s*(?:bash|sh)\\b" + ); + + @Test + void replitDoesNotExecuteTrackedZipkinJar() throws IOException { + String replit = readReplit(); + assertFalse( + replit.contains("java -jar zipkin.jar"), + "Replit launch configuration must not execute the repository-tracked opaque Zipkin JAR" + ); + } + + @Test + void replitDoesNotPipeMutableRemoteContentIntoAShell() throws IOException { + String replit = readReplit(); + assertFalse( + REMOTE_SHELL_PIPE.matcher(replit).find(), + "Replit launch configuration must not pipe mutable remote content directly into a shell" + ); + } + + @Test + void projectRunButtonDelegatesToOneMicroserviceTopology() throws IOException { + String projectWorkflow = workflowBlock(readReplit(), "Project"); + int serviceTopologyDelegates = countOccurrences(projectWorkflow, "args = \"Run Microservices\"") + + countOccurrences(projectWorkflow, "args = \"Run Eureka Server\"") + + countOccurrences(projectWorkflow, "args = \"Build and Run Microservices\""); + + assertEquals( + 1, + serviceTopologyDelegates, + "The Replit run button must not launch overlapping copies of the same microservice topology" + ); + assertTrue( + projectWorkflow.contains("args = \"Run Microservices\""), + "The Replit run button must delegate service startup to the canonical Run Microservices workflow" + ); + } + + private static String readReplit() throws IOException { + return Files.readString(PROJECT_ROOT.resolve(".replit"), StandardCharsets.UTF_8); + } + + private static String workflowBlock(String replit, String workflowName) { + String normalizedReplit = replit.replace("\r\n", "\n").replace('\r', '\n'); + String marker = "[[workflows.workflow]]\nname = \"" + workflowName + "\""; + int start = normalizedReplit.indexOf(marker); + if (start < 0) { + throw new AssertionError("Missing Replit workflow: " + workflowName); + } + int end = normalizedReplit.indexOf("[[workflows.workflow]]", start + marker.length()); + return end < 0 ? normalizedReplit.substring(start) : normalizedReplit.substring(start, end); + } + + private static int countOccurrences(String text, String needle) { + int count = 0; + int cursor = 0; + while ((cursor = text.indexOf(needle, cursor)) >= 0) { + count++; + cursor += needle.length(); + } + return count; + } + + private static Path projectRoot() { + Path current = Paths.get(System.getProperty("user.dir")).toAbsolutePath(); + while (current != null) { + if (Files.isRegularFile(current.resolve("pom.xml")) + && Files.isRegularFile(current.resolve(".replit"))) { + return current; + } + current = current.getParent(); + } + throw new IllegalStateException("Could not locate mightyETL repository root"); + } +}