|
24 | 24 | import org.junit.jupiter.params.provider.Arguments; |
25 | 25 | import org.junit.jupiter.params.provider.MethodSource; |
26 | 26 | import org.jvnet.hudson.test.Issue; |
| 27 | +import org.jvnet.hudson.test.junit.jupiter.WithJenkins; |
27 | 28 |
|
28 | 29 | /** |
29 | 30 | * Security test that proves the environment variable approach prevents |
|
35 | 36 | * |
36 | 37 | * @author Mark Waite |
37 | 38 | */ |
| 39 | +@WithJenkins |
38 | 40 | class CliGitAPISecurityTest { |
39 | 41 |
|
40 | 42 | @TempDir |
@@ -286,4 +288,178 @@ private void executeWrapper(Path wrapper, Path keyFile) throws Exception { |
286 | 288 | // That's fine - we're just checking for injection |
287 | 289 | } |
288 | 290 | } |
| 291 | + |
| 292 | + /** |
| 293 | + * Test that SSH verbose mode is disabled by default (no -vvv flag) |
| 294 | + */ |
| 295 | + @Test |
| 296 | + @Issue("JENKINS-71461") |
| 297 | + void testSshVerboseModeDisabledByDefault() throws Exception { |
| 298 | + workspace = new File(tempDir, "test-default"); |
| 299 | + workspace.mkdirs(); |
| 300 | + |
| 301 | + Path keyFile = createMockSSHKey(workspace); |
| 302 | + Path knownHosts = Files.createTempFile("known_hosts", ""); |
| 303 | + |
| 304 | + try { |
| 305 | + // When Jenkins is not available, SSH verbose should default to false |
| 306 | + GitClient gitClient = Git.with(TaskListener.NULL, new EnvVars()) |
| 307 | + .in(workspace) |
| 308 | + .using("git") |
| 309 | + .getClient(); |
| 310 | + CliGitAPIImpl git = (CliGitAPIImpl) gitClient; |
| 311 | + |
| 312 | + Path sshWrapper; |
| 313 | + if (isWindows()) { |
| 314 | + sshWrapper = git.createWindowsGitSSH(keyFile, "testuser", knownHosts); |
| 315 | + } else { |
| 316 | + sshWrapper = git.createUnixGitSSH(keyFile, "testuser", knownHosts); |
| 317 | + } |
| 318 | + |
| 319 | + String wrapperContent = Files.readString(sshWrapper, StandardCharsets.UTF_8); |
| 320 | + |
| 321 | + // Verify -vvv flag is NOT present |
| 322 | + assertFalse( |
| 323 | + wrapperContent.contains("-vvv"), |
| 324 | + "Wrapper should NOT contain -vvv flag when verbose mode is disabled"); |
| 325 | + |
| 326 | + } finally { |
| 327 | + Files.deleteIfExists(knownHosts); |
| 328 | + } |
| 329 | + } |
| 330 | + |
| 331 | + /** |
| 332 | + * Test that SSH verbose mode adds -vvv flag when enabled |
| 333 | + */ |
| 334 | + @Test |
| 335 | + @Issue("JENKINS-71461") |
| 336 | + void testSshVerboseModeEnabled() throws Exception { |
| 337 | + // Skip if Jenkins instance is not available |
| 338 | + if (jenkins.model.Jenkins.getInstanceOrNull() == null) { |
| 339 | + return; |
| 340 | + } |
| 341 | + |
| 342 | + workspace = new File(tempDir, "test-verbose"); |
| 343 | + workspace.mkdirs(); |
| 344 | + |
| 345 | + Path keyFile = createMockSSHKey(workspace); |
| 346 | + Path knownHosts = Files.createTempFile("known_hosts", ""); |
| 347 | + |
| 348 | + try { |
| 349 | + // Enable SSH verbose mode |
| 350 | + GitHostKeyVerificationConfiguration.get().setSshVerbose(true); |
| 351 | + |
| 352 | + GitClient gitClient = Git.with(TaskListener.NULL, new EnvVars()) |
| 353 | + .in(workspace) |
| 354 | + .using("git") |
| 355 | + .getClient(); |
| 356 | + CliGitAPIImpl git = (CliGitAPIImpl) gitClient; |
| 357 | + |
| 358 | + Path sshWrapper; |
| 359 | + if (isWindows()) { |
| 360 | + sshWrapper = git.createWindowsGitSSH(keyFile, "testuser", knownHosts); |
| 361 | + } else { |
| 362 | + sshWrapper = git.createUnixGitSSH(keyFile, "testuser", knownHosts); |
| 363 | + } |
| 364 | + |
| 365 | + String wrapperContent = Files.readString(sshWrapper, StandardCharsets.UTF_8); |
| 366 | + |
| 367 | + // Verify -vvv flag IS present |
| 368 | + assertTrue( |
| 369 | + wrapperContent.contains("-vvv"), "Wrapper should contain -vvv flag when verbose mode is enabled"); |
| 370 | + |
| 371 | + } finally { |
| 372 | + // Reset to default |
| 373 | + GitHostKeyVerificationConfiguration.get().setSshVerbose(false); |
| 374 | + Files.deleteIfExists(knownHosts); |
| 375 | + } |
| 376 | + } |
| 377 | + |
| 378 | + /** |
| 379 | + * Test that SSH verbose mode flag is placed correctly in Unix wrapper |
| 380 | + */ |
| 381 | + @Test |
| 382 | + @Issue("JENKINS-71461") |
| 383 | + void testUnixSshVerboseFlagPlacement() throws Exception { |
| 384 | + // Skip if Jenkins instance is not available or on Windows |
| 385 | + if (jenkins.model.Jenkins.getInstanceOrNull() == null || isWindows()) { |
| 386 | + return; |
| 387 | + } |
| 388 | + |
| 389 | + workspace = new File(tempDir, "test-unix-verbose"); |
| 390 | + workspace.mkdirs(); |
| 391 | + |
| 392 | + Path keyFile = createMockSSHKey(workspace); |
| 393 | + Path knownHosts = Files.createTempFile("known_hosts", ""); |
| 394 | + |
| 395 | + try { |
| 396 | + // Enable SSH verbose mode |
| 397 | + GitHostKeyVerificationConfiguration.get().setSshVerbose(true); |
| 398 | + |
| 399 | + GitClient gitClient = Git.with(TaskListener.NULL, new EnvVars()) |
| 400 | + .in(workspace) |
| 401 | + .using("git") |
| 402 | + .getClient(); |
| 403 | + CliGitAPIImpl git = (CliGitAPIImpl) gitClient; |
| 404 | + Path sshWrapper = git.createUnixGitSSH(keyFile, "testuser", knownHosts); |
| 405 | + |
| 406 | + String wrapperContent = Files.readString(sshWrapper, StandardCharsets.UTF_8); |
| 407 | + |
| 408 | + // Verify -vvv appears before "$@" (which represents additional args) |
| 409 | + int vvvIndex = wrapperContent.indexOf("-vvv"); |
| 410 | + int argsIndex = wrapperContent.indexOf("\"$@\""); |
| 411 | + assertTrue(vvvIndex > 0, "-vvv flag should be present"); |
| 412 | + assertTrue(argsIndex > 0, "\"$@\" should be present"); |
| 413 | + assertTrue(vvvIndex < argsIndex, "-vvv flag should appear before \"$@\""); |
| 414 | + |
| 415 | + } finally { |
| 416 | + // Reset to default |
| 417 | + GitHostKeyVerificationConfiguration.get().setSshVerbose(false); |
| 418 | + Files.deleteIfExists(knownHosts); |
| 419 | + } |
| 420 | + } |
| 421 | + |
| 422 | + /** |
| 423 | + * Test that SSH verbose mode flag is placed correctly in Windows wrapper |
| 424 | + */ |
| 425 | + @Test |
| 426 | + @Issue("JENKINS-71461") |
| 427 | + void testWindowsSshVerboseFlagPlacement() throws Exception { |
| 428 | + // Skip if Jenkins instance is not available or on Unix |
| 429 | + if (jenkins.model.Jenkins.getInstanceOrNull() == null || !isWindows()) { |
| 430 | + return; // Skip on Unix |
| 431 | + } |
| 432 | + |
| 433 | + workspace = new File(tempDir, "test-windows-verbose"); |
| 434 | + workspace.mkdirs(); |
| 435 | + |
| 436 | + Path keyFile = createMockSSHKey(workspace); |
| 437 | + Path knownHosts = Files.createTempFile("known_hosts", ""); |
| 438 | + |
| 439 | + try { |
| 440 | + // Enable SSH verbose mode |
| 441 | + GitHostKeyVerificationConfiguration.get().setSshVerbose(true); |
| 442 | + |
| 443 | + GitClient gitClient = Git.with(TaskListener.NULL, new EnvVars()) |
| 444 | + .in(workspace) |
| 445 | + .using("git") |
| 446 | + .getClient(); |
| 447 | + CliGitAPIImpl git = (CliGitAPIImpl) gitClient; |
| 448 | + Path sshWrapper = git.createWindowsGitSSH(keyFile, "testuser", knownHosts); |
| 449 | + |
| 450 | + String wrapperContent = Files.readString(sshWrapper, StandardCharsets.UTF_8); |
| 451 | + |
| 452 | + // Verify -vvv appears before %* (which represents additional args) |
| 453 | + int vvvIndex = wrapperContent.indexOf("-vvv"); |
| 454 | + int argsIndex = wrapperContent.indexOf("%*"); |
| 455 | + assertTrue(vvvIndex > 0, "-vvv flag should be present"); |
| 456 | + assertTrue(argsIndex > 0, "%* should be present"); |
| 457 | + assertTrue(vvvIndex < argsIndex, "-vvv flag should appear before %*"); |
| 458 | + |
| 459 | + } finally { |
| 460 | + // Reset to default |
| 461 | + GitHostKeyVerificationConfiguration.get().setSshVerbose(false); |
| 462 | + Files.deleteIfExists(knownHosts); |
| 463 | + } |
| 464 | + } |
289 | 465 | } |
0 commit comments