|
2 | 2 |
|
3 | 3 | import static org.hamcrest.MatcherAssert.assertThat; |
4 | 4 | import static org.hamcrest.Matchers.*; |
| 5 | +import static org.junit.jupiter.api.Assumptions.assumeTrue; |
5 | 6 |
|
6 | 7 | import com.cloudbees.plugins.credentials.CredentialsProvider; |
7 | 8 | import com.cloudbees.plugins.credentials.CredentialsScope; |
|
12 | 13 | import com.cloudbees.plugins.credentials.domains.Domain; |
13 | 14 | import com.cloudbees.plugins.credentials.impl.CertificateCredentialsImpl; |
14 | 15 | import com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl; |
| 16 | + |
| 17 | +import hudson.model.Descriptor; |
15 | 18 | import hudson.model.Descriptor.FormException; |
16 | 19 | import hudson.model.Fingerprint; |
| 20 | +import hudson.model.Label; |
| 21 | +import hudson.model.Node; |
17 | 22 | import hudson.model.Result; |
18 | 23 |
|
19 | 24 | import java.io.ByteArrayOutputStream; |
20 | 25 | import java.io.File; |
21 | 26 | import java.io.IOException; |
22 | 27 | import java.nio.file.Files; |
23 | 28 | import java.util.Collections; |
| 29 | + |
| 30 | +import hudson.model.Slave; |
24 | 31 | import jenkins.model.Jenkins; |
25 | 32 |
|
26 | 33 | import org.apache.commons.io.FileUtils; |
@@ -66,6 +73,15 @@ class HttpRequestStepCredentialsTest extends HttpRequestTestBase { |
66 | 73 | */ |
67 | 74 | private final boolean verbosePipelines = false; |
68 | 75 |
|
| 76 | + // Data for build agent setup |
| 77 | + /** Build agent label expected by test cases for remote logic execution |
| 78 | + * and data transfer [JENKINS-70101] */ |
| 79 | + private final static String agentLabelString = "cred-test-worker"; |
| 80 | + // Can this be reused for many test cases? |
| 81 | + private Slave agent = null; |
| 82 | + /** Tri-state Unknown/started/not usable [JENKINS-70101] */ |
| 83 | + private Boolean agentUsable = null; |
| 84 | + |
69 | 85 | // From CertificateCredentialImplTest in credentials-plugin |
70 | 86 | /** Temporary location for keystore files. |
71 | 87 | * @see #p12simple |
@@ -93,6 +109,34 @@ class HttpRequestStepCredentialsTest extends HttpRequestTestBase { |
93 | 109 | */ |
94 | 110 | private CredentialsStore store = null; |
95 | 111 |
|
| 112 | + /** True if we can use remote agent tests, and the credentials plugin version |
| 113 | + * here is expected to transfer secret data across the Channel correctly |
| 114 | + * (assuming issue JENKINS-70101 is fixed in that plugin). |
| 115 | + */ |
| 116 | + static private Boolean credentialsPluginDoesSnapshotsRight = null; |
| 117 | + static { |
| 118 | + try { |
| 119 | + Class.forName( |
| 120 | + "com.cloudbees.plugins.credentials.impl.CertificateCredentialsSnapshotTaker", |
| 121 | + false, HttpRequestStepCredentialsTest.class.getClassLoader()); |
| 122 | + credentialsPluginDoesSnapshotsRight = true; |
| 123 | + } catch (ClassNotFoundException ignored) { |
| 124 | + credentialsPluginDoesSnapshotsRight = false; |
| 125 | + } catch (ExceptionInInitializerError ignored) { |
| 126 | + // Per https://www.baeldung.com/java-check-class-exists the Class.forName() |
| 127 | + // calls a static initializer which may fail (at least for the default |
| 128 | + // single-argument version of the method), but still -- if we get that far, |
| 129 | + // the class exists so we are probably running the version of plugin with |
| 130 | + // https://github.com/jenkinsci/credentials-plugin/pull/391 |
| 131 | + credentialsPluginDoesSnapshotsRight = true; |
| 132 | + } |
| 133 | + } |
| 134 | + /** Honour the check via {@link #credentialsPluginDoesSnapshotsRight} [false], |
| 135 | + * or try (and possibly fail) with any implementation/version of the |
| 136 | + * credentials plugin [true]? |
| 137 | + */ |
| 138 | + private Boolean credentialsPluginTestRemoteAlways = false; |
| 139 | + |
96 | 140 | private static StandardCredentials getInvalidCredential() throws FormException { |
97 | 141 | String username = "bad-user"; |
98 | 142 | String password = "bad-password"; |
@@ -166,6 +210,9 @@ void enableSystemCredentialsProvider() { |
166 | 210 | } |
167 | 211 | } |
168 | 212 | assertThat("The system credentials provider is enabled", store, notNullValue()); |
| 213 | + |
| 214 | + // Primarily needed for remote-agent tests per JENKINS-70101? |
| 215 | + j.jenkins.setCrumbIssuer(null); |
169 | 216 | } |
170 | 217 |
|
171 | 218 | ///////////////////////////////////////////////////////////////// |
@@ -352,9 +399,10 @@ private String cpsScriptCertCredentialTestHttpRequest(String runnerTag) { |
352 | 399 | ///////////////////////////////////////////////////////////////// |
353 | 400 |
|
354 | 401 | // A set of tests with certificate credentials in different contexts |
355 | | - // TODO: Test on remote agent as in https://github.com/jenkinsci/credentials-plugin/pull/391 |
356 | | - // but this requires that PR to be merged first, so credentials-plugin |
357 | | - // processes snapshot() and readable keystore data gets to remote agent. |
| 402 | + // NOTE: Test cases on remote agent require the PR |
| 403 | + // https://github.com/jenkinsci/credentials-plugin/pull/391 |
| 404 | + // to be merged first, so credentials-plugin processes snapshot() |
| 405 | + // and readable keystore data gets to remote agent. |
358 | 406 | // Note that the tests below focus on ability of the plugin to load and |
359 | 407 | // process the key store specified by the credential, rather than that |
360 | 408 | // it is usable further. It would be a separate effort to mock up a web |
@@ -491,6 +539,86 @@ void testCertTrustedHttpRequestOnNodeLocal() throws Exception { |
491 | 539 | j.assertLogContains("Treating UnknownHostException", run); |
492 | 540 | } |
493 | 541 |
|
| 542 | + ///////////////////////////////////////////////////////////////// |
| 543 | + // Helpers for pipeline tests with remote agents |
| 544 | + ///////////////////////////////////////////////////////////////// |
| 545 | + |
| 546 | + private Boolean isAvailableAgent() { |
| 547 | + // Can be used to skip optional tests if we know we could not set up an agent |
| 548 | + if (agent == null) |
| 549 | + return false; |
| 550 | + return agentUsable; |
| 551 | + } |
| 552 | + |
| 553 | + private Boolean setupAgent() throws OutOfMemoryError, Exception { |
| 554 | + if (isAvailableAgent()) |
| 555 | + return true; |
| 556 | + |
| 557 | + // See how credentialsPluginTestRemoteAlways is determined above |
| 558 | + // and revise if the ultimately merged fix that started as |
| 559 | + // https://github.com/jenkinsci/credentials-plugin/pull/391 |
| 560 | + // gets changed before the merge or later on... |
| 561 | + String msg_70101 = "This test needs a version of credentials-plugin with a fix for JENKINS-70101, and that does not seem to be deployed here"; |
| 562 | + if (!credentialsPluginTestRemoteAlways) |
| 563 | + assumeTrue(credentialsPluginDoesSnapshotsRight, msg_70101); |
| 564 | + |
| 565 | + // else: credentialsPluginTestRemoteAlways, even if we fail |
| 566 | + if (!credentialsPluginDoesSnapshotsRight) { |
| 567 | + System.err.println("WARNING: " + msg_70101 + "; this test run was configured to try remote agents anyway"); |
| 568 | + // return false; |
| 569 | + } |
| 570 | + |
| 571 | + // Note we anticipate this might fail e.g. due to system resources; |
| 572 | + // it should not block the whole test suite from running |
| 573 | + // (we would just dynamically skip certain test cases) |
| 574 | + try { |
| 575 | + // Define a "Permanent Agent" |
| 576 | + Label agentLabel = Label.get(agentLabelString); |
| 577 | + agent = j.createOnlineSlave(agentLabel); |
| 578 | + agent.setNodeDescription("Worker in another JVM, remoting used"); |
| 579 | + agent.setNumExecutors(1); |
| 580 | + agent.setMode(Node.Mode.EXCLUSIVE); |
| 581 | + ///agent.setRetentionStrategy(new RetentionStrategy.Always()); |
| 582 | + |
| 583 | +/* |
| 584 | + // Add node envvars |
| 585 | + List<Entry> env = new ArrayList<Entry>(); |
| 586 | + env.add(new Entry("key1","value1")); |
| 587 | + env.add(new Entry("key2","value2")); |
| 588 | + EnvironmentVariablesNodeProperty envPro = new EnvironmentVariablesNodeProperty(env); |
| 589 | + agent.getNodeProperties().add(envPro); |
| 590 | +*/ |
| 591 | + |
| 592 | + String agentLog = null; |
| 593 | + agentUsable = false; |
| 594 | + for (long i = 0; i < 5; i++) { |
| 595 | + Thread.sleep(1000); |
| 596 | + agentLog = agent.getComputer().getLog(); |
| 597 | + if (i == 2 && (agentLog == null || agentLog.isEmpty())) { |
| 598 | + // Give it a little time to autostart, then kick it up if needed: |
| 599 | + agent.getComputer().connect(true); // "always" should have started it; avoid duplicate runs |
| 600 | + } |
| 601 | + if (agentLog != null && agentLog.contains("Agent successfully connected and online")) { |
| 602 | + agentUsable = true; |
| 603 | + break; |
| 604 | + } |
| 605 | + } |
| 606 | + System.out.println("Spawned build agent " + |
| 607 | + "usability: " + agentUsable.toString() + |
| 608 | + "; connection log:" + (agentLog == null ? " <null>" : "\n" + agentLog)); |
| 609 | + } catch (Descriptor.FormException | NullPointerException e) { |
| 610 | + agentUsable = false; |
| 611 | + } |
| 612 | + |
| 613 | + return agentUsable; |
| 614 | + } |
| 615 | + |
| 616 | + ///////////////////////////////////////////////////////////////// |
| 617 | + // Certificate credentials retrievability by http-request-plugin |
| 618 | + // in a set of local+remote JVMs (should work with versions of |
| 619 | + // credentials plugin where issue JENKINS-70101 is fixed) |
| 620 | + ///////////////////////////////////////////////////////////////// |
| 621 | + |
494 | 622 | /** Simplified version of simple/trusted tests with "myCert" credential id, |
495 | 623 | * transplanted from https://github.com/jenkinsci/credentials-plugin/pull/391 : |
496 | 624 | * Check that Certificate credentials are usable with pipeline script |
@@ -548,4 +676,136 @@ void testCertHttpRequestOnNodeLocal() throws Exception { |
548 | 676 | j.assertLogContains("HTTP Request Plugin Response: ", run); |
549 | 677 | j.assertLogContains("Using authentication: myCert", run); |
550 | 678 | } |
| 679 | + |
| 680 | + /** |
| 681 | + * Check that Certificate credentials are usable with pipeline script |
| 682 | + * running on a remote {@code node{}} with separate JVM (e.g. |
| 683 | + * check that remoting and credential snapshot work properly). |
| 684 | + */ |
| 685 | + @Test |
| 686 | + @Issue("JENKINS-70101") |
| 687 | + void testCertHttpRequestOnNodeRemote() throws Exception { |
| 688 | + assumeTrue(this.setupAgent() == true, "This test needs a separate build agent"); |
| 689 | + |
| 690 | + prepareUploadedKeystore(); |
| 691 | + |
| 692 | + // Configure the build to use the credential |
| 693 | + WorkflowJob proj = j.jenkins.createProject(WorkflowJob.class, "proj"); |
| 694 | + String script = |
| 695 | + cpsScriptCredentialTestImports() + |
| 696 | + "node(\"" + agentLabelString + "\") {\n" + |
| 697 | + cpsScriptCertCredentialTestHttpRequest("REMOTE NODE") + |
| 698 | + "}\n"; |
| 699 | + proj.setDefinition(new CpsFlowDefinition(script, false)); |
| 700 | + |
| 701 | + // Execute the build |
| 702 | + WorkflowRun run = proj.scheduleBuild2(0).get(); |
| 703 | + if (verbosePipelines) System.out.println(getLogAsStringPlaintext(run)); |
| 704 | + |
| 705 | + // Check expectations |
| 706 | + j.assertBuildStatus(Result.SUCCESS, run); |
| 707 | + // Got to the end? |
| 708 | + j.assertLogContains("HTTP Request Plugin Response: ", run); |
| 709 | + } |
| 710 | + |
| 711 | + ///////////////////////////////////////////////////////////////// |
| 712 | + // User/pass credentials tests |
| 713 | + ///////////////////////////////////////////////////////////////// |
| 714 | + |
| 715 | + // Partially from UsernamePasswordCredentialsImplTest setup() |
| 716 | + private void prepareUsernamePassword() throws IOException, FormException { |
| 717 | + UsernamePasswordCredentialsImpl credentials = |
| 718 | + new UsernamePasswordCredentialsImpl(null, |
| 719 | + "abc123", "Bob’s laptop", |
| 720 | + "bob", "s3cr3t"); |
| 721 | + SystemCredentialsProvider.getInstance().getCredentials().add(credentials); |
| 722 | + SystemCredentialsProvider.getInstance().save(); |
| 723 | + } |
| 724 | + |
| 725 | + private String cpsScriptUsernamePasswordCredentialTestHttpRequest(String runnerTag) { |
| 726 | + return cpsScriptCredentialTestHttpRequest("abc123", runnerTag, false); |
| 727 | + } |
| 728 | + |
| 729 | + /** Check that Username credentials are usable with pipeline script |
| 730 | + * running without a {@code node{}} block. |
| 731 | + */ |
| 732 | + @Test |
| 733 | + @Issue("JENKINS-70101") |
| 734 | + void testUsernamePasswordHttpRequestOnController() throws Exception { |
| 735 | + prepareUsernamePassword(); |
| 736 | + |
| 737 | + // Configure the build to use the credential |
| 738 | + WorkflowJob proj = j.jenkins.createProject(WorkflowJob.class, "proj"); |
| 739 | + String script = |
| 740 | + cpsScriptUsernamePasswordCredentialTestHttpRequest("CONTROLLER BUILT-IN"); |
| 741 | + proj.setDefinition(new CpsFlowDefinition(script, false)); |
| 742 | + |
| 743 | + // Execute the build |
| 744 | + WorkflowRun run = proj.scheduleBuild2(0).get(); |
| 745 | + if (verbosePipelines) System.out.println(getLogAsStringPlaintext(run)); |
| 746 | + |
| 747 | + // Check expectations |
| 748 | + j.assertBuildStatus(Result.SUCCESS, run); |
| 749 | + // Got to the end? |
| 750 | + j.assertLogContains("HTTP Request Plugin Response: ", run); |
| 751 | + } |
| 752 | + |
| 753 | + /** Check that Username credentials are usable with pipeline script |
| 754 | + * running on a {@code node{}} (provided by the controller JVM). |
| 755 | + */ |
| 756 | + @Test |
| 757 | + @Issue("JENKINS-70101") |
| 758 | + void testUsernamePasswordHttpRequestOnNodeLocal() throws Exception { |
| 759 | + prepareUsernamePassword(); |
| 760 | + |
| 761 | + // Configure the build to use the credential |
| 762 | + WorkflowJob proj = j.jenkins.createProject(WorkflowJob.class, "proj"); |
| 763 | + String script = |
| 764 | + "node {\n" + |
| 765 | + cpsScriptUsernamePasswordCredentialTestHttpRequest("CONTROLLER NODE") + |
| 766 | + "}\n"; |
| 767 | + proj.setDefinition(new CpsFlowDefinition(script, false)); |
| 768 | + |
| 769 | + // Execute the build |
| 770 | + WorkflowRun run = proj.scheduleBuild2(0).get(); |
| 771 | + if (verbosePipelines) System.out.println(getLogAsStringPlaintext(run)); |
| 772 | + |
| 773 | + // Check expectations |
| 774 | + j.assertBuildStatus(Result.SUCCESS, run); |
| 775 | + // Got to the end? |
| 776 | + j.assertLogContains("HTTP Request Plugin Response: ", run); |
| 777 | + } |
| 778 | + |
| 779 | + /** |
| 780 | + * Check that Username credentials are usable with pipeline script |
| 781 | + * running on a remote {@code node{}} with separate JVM (e.g. |
| 782 | + * check that remoting and credential snapshot work properly). |
| 783 | + */ |
| 784 | + @Test |
| 785 | + @Issue("JENKINS-70101") |
| 786 | + void testUsernamePasswordHttpRequestOnNodeRemote() throws Exception { |
| 787 | + // Check that credentials are usable with pipeline script |
| 788 | + // running on a remote node{} with separate JVM (check |
| 789 | + // that remoting/snapshot work properly) |
| 790 | + assumeTrue(this.setupAgent() == true, "This test needs a separate build agent"); |
| 791 | + |
| 792 | + prepareUsernamePassword(); |
| 793 | + |
| 794 | + // Configure the build to use the credential |
| 795 | + WorkflowJob proj = j.jenkins.createProject(WorkflowJob.class, "proj"); |
| 796 | + String script = |
| 797 | + "node(\"" + agentLabelString + "\") {\n" + |
| 798 | + cpsScriptUsernamePasswordCredentialTestHttpRequest("REMOTE NODE") + |
| 799 | + "}\n"; |
| 800 | + proj.setDefinition(new CpsFlowDefinition(script, false)); |
| 801 | + |
| 802 | + // Execute the build |
| 803 | + WorkflowRun run = proj.scheduleBuild2(0).get(); |
| 804 | + if (verbosePipelines) System.out.println(getLogAsStringPlaintext(run)); |
| 805 | + |
| 806 | + // Check expectations |
| 807 | + j.assertBuildStatus(Result.SUCCESS, run); |
| 808 | + // Got to the end? |
| 809 | + j.assertLogContains("HTTP Request Plugin Response: ", run); |
| 810 | + } |
551 | 811 | } |
0 commit comments