diff --git a/src/test/java/jenkins/plugins/http_request/HttpRequestStepCredentialsTest.java b/src/test/java/jenkins/plugins/http_request/HttpRequestStepCredentialsTest.java index f60b642..c1e972f 100644 --- a/src/test/java/jenkins/plugins/http_request/HttpRequestStepCredentialsTest.java +++ b/src/test/java/jenkins/plugins/http_request/HttpRequestStepCredentialsTest.java @@ -2,6 +2,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import com.cloudbees.plugins.credentials.CredentialsProvider; import com.cloudbees.plugins.credentials.CredentialsScope; @@ -12,8 +13,12 @@ import com.cloudbees.plugins.credentials.domains.Domain; import com.cloudbees.plugins.credentials.impl.CertificateCredentialsImpl; import com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl; + +import hudson.model.Descriptor; import hudson.model.Descriptor.FormException; import hudson.model.Fingerprint; +import hudson.model.Label; +import hudson.model.Node; import hudson.model.Result; import java.io.ByteArrayOutputStream; @@ -21,6 +26,8 @@ import java.io.IOException; import java.nio.file.Files; import java.util.Collections; + +import hudson.model.Slave; import jenkins.model.Jenkins; import org.apache.commons.io.FileUtils; @@ -37,29 +44,100 @@ import org.jvnet.hudson.test.junit.jupiter.WithJenkins; /** + * The HttpRequestStepCredentialsTest suite prepares pipeline scripts to + * retrieve some previously saved credentials, on the controller, + * on a node provided by it, and on a worker agent in separate JVM. + * This picks known-working test cases and their setup from other + * test classes which address those credential types in more detail.
+ * + * Initially tied to JENKINS-70101 research, and tests on remote agent + * would require + * PR credentials-plugin#391 + * to be merged first, so credentials-plugin processes {@code snapshot()} + * properly and readable keystore data gets to remote agent.
+ * + * So part of this is a mixed test suite of two plugins, making sure that + * the chosen versions do cooperate correctly for our ultimate needs.
+ * * @author Mark Waite + * @author Jim Klimov */ @WithJenkins class HttpRequestStepCredentialsTest extends HttpRequestTestBase { - // For developers: set to `true` so that pipeline console logs show - // up in System.out (and/or System.err) of the plugin test run by - // mvn test -Dtest="HttpRequestStepCredentialsTest" + /** For developers: set to `true` so that pipeline console logs show + * up in {@link System#out} (and/or {@link System#err}) of the plugin + * test run executed by: + *
+     *   mvn test -Dtest="HttpRequestStepCredentialsTest"
+     * 
+ */ private final boolean verbosePipelines = false; - private String getLogAsStringPlaintext(WorkflowRun f) throws java.io.IOException { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - f.getLogText().writeLogTo(0, baos); - return baos.toString(); - } - - // From CertificateCredentialImplTest + // Data for build agent setup + /** Build agent label expected by test cases for remote logic execution + * and data transfer [JENKINS-70101] */ + private final static String agentLabelString = "cred-test-worker"; + // Can this be reused for many test cases? + private Slave agent = null; + /** Tri-state Unknown/started/not usable [JENKINS-70101] */ + private Boolean agentUsable = null; + + // From CertificateCredentialImplTest in credentials-plugin + /** Temporary location for keystore files. + * @see #p12simple + * @see #p12trusted + */ @TempDir private File tmp; + + /** A temporary (randomly-named) file with a PKCS#12 key/cert store which + * contains a private key + openvpn certs, as alias named "1" + * (according to keytool). + */ private File p12simple; + + /** A temporary (randomly-named) file with a PKCS#12 key/cert store which + * contains a private key + openvpn certs as alias named "1", + * and another alias named "ca" with trustedKeyEntry for CA + * (according to keytool). + */ private File p12trusted; + /** Reference to the system credentials provider, prepared by + * {@link #enableSystemCredentialsProvider} method + * before each test case. + */ private CredentialsStore store = null; + /** True if we can use remote agent tests, and the credentials plugin version + * here is expected to transfer secret data across the Channel correctly + * (assuming issue JENKINS-70101 is fixed in that plugin). + */ + static private Boolean credentialsPluginDoesSnapshotsRight = null; + static { + try { + Class.forName( + "com.cloudbees.plugins.credentials.impl.CertificateCredentialsSnapshotTaker" + // , false, HttpRequestStepCredentialsTest.class.getClassLoader() + ); + credentialsPluginDoesSnapshotsRight = true; + } catch (ClassNotFoundException ignored) { + credentialsPluginDoesSnapshotsRight = false; + } catch (ExceptionInInitializerError ignored) { + // Per https://www.baeldung.com/java-check-class-exists the Class.forName() + // calls a static initializer which may fail (at least for the default + // single-argument version of the method), but still -- if we get that far, + // the class exists so we are probably running the version of plugin with + // https://github.com/jenkinsci/credentials-plugin/pull/391 + credentialsPluginDoesSnapshotsRight = true; + } + } + /** Honour the check via {@link #credentialsPluginDoesSnapshotsRight} [false], + * or try (and possibly fail) with any implementation/version of the + * credentials plugin [true]? + */ + private Boolean credentialsPluginTestRemoteAlways = false; + private static StandardCredentials getInvalidCredential() throws FormException { String username = "bad-user"; String password = "bad-password"; @@ -69,29 +147,56 @@ private static StandardCredentials getInvalidCredential() throws FormException { } private StandardCredentials getCertificateCredentialSimple() throws IOException { + return getCertificateCredentialSimple("cred_cert_simple", "password"); + } + + private StandardCredentials getCertificateCredentialSimple(String id, String password) throws IOException { if (p12simple == null) { // Contains a private key + openvpn certs, // as alias named "1" (according to keytool) - p12simple = File.createTempFile("test.p12", null, tmp); + p12simple = File.createTempFile("test-keystore-", ".p12", tmp); FileUtils.copyURLToFile(HttpRequestStepCredentialsTest.class.getResource("test.p12"), p12simple); } SecretBytes uploadedKeystore = SecretBytes.fromRawBytes(Files.readAllBytes(p12simple.toPath())); CertificateCredentialsImpl.UploadedKeyStoreSource storeSource = new CertificateCredentialsImpl.UploadedKeyStoreSource(null, uploadedKeystore); - return new CertificateCredentialsImpl(null, "cred_cert_simple", null, "password", storeSource); + return new CertificateCredentialsImpl(null, id, null, password, storeSource); } private StandardCredentials getCertificateCredentialTrusted() throws IOException { + return getCertificateCredentialTrusted("cred_cert_with_ca", "password"); + } + + private StandardCredentials getCertificateCredentialTrusted(String id, String password) throws IOException { if (p12trusted == null) { // Contains a private key + openvpn certs as alias named "1", // and another alias named "ca" with trustedKeyEntry for CA - p12trusted = File.createTempFile("testTrusted.p12", null, tmp); + p12trusted = File.createTempFile("testTrusted-keystore-", ".p12", tmp); FileUtils.copyURLToFile(HttpRequestStepCredentialsTest.class.getResource("testTrusted.p12"), p12trusted); } SecretBytes uploadedKeystore = SecretBytes.fromRawBytes(Files.readAllBytes(p12trusted.toPath())); CertificateCredentialsImpl.UploadedKeyStoreSource storeSource = new CertificateCredentialsImpl.UploadedKeyStoreSource(null, uploadedKeystore); - return new CertificateCredentialsImpl(null, "cred_cert_with_ca", null, "password", storeSource); + return new CertificateCredentialsImpl(null, id, null, password, storeSource); + } + + /** Get a new CertificateCredentialsImpl() and save it into the {@link #store} */ + private void prepareUploadedKeystore(String id, String password, Boolean useSimple) throws IOException { + StandardCredentials c = (useSimple + ? getCertificateCredentialSimple(id, password) + : getCertificateCredentialTrusted(id, password) + ); + SystemCredentialsProvider.getInstance().getCredentials().add(c); + SystemCredentialsProvider.getInstance().save(); + } + + private void prepareUploadedKeystore(String id, String password) throws IOException { + prepareUploadedKeystore(id, password, true); + } + + // Partially from certificate-plugin CertificateCredentialImplTest setup() + private void prepareUploadedKeystore() throws IOException { + prepareUploadedKeystore("myCert", "password"); } @BeforeEach @@ -106,8 +211,16 @@ void enableSystemCredentialsProvider() { } } assertThat("The system credentials provider is enabled", store, notNullValue()); + + // Primarily needed for remote-agent tests per JENKINS-70101? + j.jenkins.setCrumbIssuer(null); } + ///////////////////////////////////////////////////////////////// + // Test cases + ///////////////////////////////////////////////////////////////// + + /** A credentials tracking test */ @Test void trackCredentials() throws Exception { StandardCredentials credential = getInvalidCredential(); @@ -162,22 +275,80 @@ void trackCredentials() throws Exception { assertThat(page.getAnchorByText(proj.getFullDisplayName()), notNullValue()); } - // A set of tests with certificate credentials in different contexts - // TODO: Test on remote agent as in https://github.com/jenkinsci/credentials-plugin/pull/391 - // but this requires that PR to be merged first, so credentials-plugin - // processes snapshot() and readable keystore data gets to remote agent. - // Note that the tests below focus on ability of the plugin to load and - // process the key store specified by the credential, rather than that - // it is usable further. It would be a separate effort to mock up a web - // server protected by HTTPS and using certificates for login (possibly - // user and server backed by two different CA's), and query that. - private static String cpsScriptCredentialTestHttpRequest(String id, String runnerTag) { - // Note: we accept any outcome (for the plugin, unresolved host is HTTP-404) - // but it may not crash making use of the credential - // Note: cases withLocalCertLookup also need cpsScriptCredentialTestImports() + ///////////////////////////////////////////////////////////////// + // Helpers for pipeline tests about credentials retrievability + // by http-request-plugin (on same or remote JVM) + ///////////////////////////////////////////////////////////////// + + private String getLogAsStringPlaintext(WorkflowRun f) throws java.io.IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + f.getLogText().writeLogTo(0, baos); + return baos.toString(); + } + + /** Returns a String with prepared part of the pipeline script with imports used by some other snippet generators */ + private String cpsScriptCredentialTestImports() { + return "import com.cloudbees.plugins.credentials.CredentialsMatchers;\n" + + "import com.cloudbees.plugins.credentials.CredentialsProvider;\n" + + "import com.cloudbees.plugins.credentials.common.StandardCertificateCredentials;\n" + + "import com.cloudbees.plugins.credentials.common.StandardCredentials;\n" + + "import com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials;\n" + + "import com.cloudbees.plugins.credentials.domains.URIRequirementBuilder;\n" + + "import com.cloudbees.plugins.credentials.impl.CertificateCredentialsImpl;\n" + + "import com.cloudbees.plugins.credentials.impl.CertificateCredentialsImpl.KeyStoreSource;\n" + + "import hudson.security.ACL;\n" + + "import java.security.KeyStore;\n" + + "\n"; + } + + /** Returns a String with prepared part of the pipeline script with a request + * (to non-existent site) using a credential named by "id" parameter.
+ * + * The test class property {@link #verbosePipelines} can be used to toggle writing + * a copy of the progress message(s) to {@link System#out} and {@link System#err} + * of the build agent JVM.
+ * + * Note: we accept any outcome for the HTTP request (for this plugin, unresolved + * host is HTTP-404) but it may not crash making use of the credential.
+ * + * @param id Credential ID, saved earlier into the store + * @param runnerTag Reported in pipeline build log + * @param withReentrability If true, generate a second request with same credential, + * to make sure it is not garbled etc. by first use. + * @param withLocalCertLookup If true, add lookup and logging of keystore data + * (into the pipeline build console, optionally also system streams). + * Note: test cases {@code withLocalCertLookup} need to + * generate {@link #cpsScriptCredentialTestImports} into + * their pipelines first. + * @return String with prepared part of pipeline script + */ + private String cpsScriptCredentialTestHttpRequest(String id, String runnerTag, Boolean withReentrability, Boolean withLocalCertLookup) { return "def authentication='" + id + "';\n" + "\n" - + "echo \"Querying HTTPS with credential...\"\n" + + "def msg\n" + + (withLocalCertLookup ? ( + "if (true) { // scoping\n" + + " msg = \"Finding credential with id='${authentication}'...\"\n" + + " echo msg;" + (verbosePipelines ? " System.out.println(msg); System.err.println(msg)" : "" ) + ";\n" + + " StandardCredentials credential = CredentialsMatchers.firstOrNull(\n" + + " CredentialsProvider.lookupCredentials(\n" + + " StandardCredentials.class,\n" + + " Jenkins.instance, null, null),\n" + + " CredentialsMatchers.withId(authentication));\n" + + " msg = \"Getting keystore...\"\n" + + " echo msg;" + (verbosePipelines ? " System.out.println(msg); System.err.println(msg)" : "" ) + ";\n" + + " KeyStore keyStore = credential.getKeyStore();\n" + + " msg = \"Getting keystore source...\"\n" + + " echo msg;" + (verbosePipelines ? " System.out.println(msg); System.err.println(msg)" : "" ) + ";\n" + + " KeyStoreSource kss = ((CertificateCredentialsImpl) credential).getKeyStoreSource();\n" + + " msg = \"Getting keystore source bytes...\"\n" + + " echo msg;" + (verbosePipelines ? " System.out.println(msg); System.err.println(msg)" : "" ) + ";\n" + + " byte[] kssb = kss.getKeyStoreBytes();\n" + + "}\n" ) + : "" ) + + "\n" + + "msg = \"Querying HTTPS with credential on " + (runnerTag != null ? runnerTag : "") + "...\"\n" + + "echo msg;" + (verbosePipelines ? " System.out.println(msg); System.err.println(msg)" : "" ) + ";\n" + "def response = httpRequest(url: 'https://github.xcom/api/v3',\n" + " httpMode: 'GET',\n" + " authentication: authentication,\n" @@ -185,34 +356,74 @@ private static String cpsScriptCredentialTestHttpRequest(String id, String runne + " contentType : 'APPLICATION_FORM',\n" + " validResponseCodes: '100:599',\n" + " quiet: false)\n" - + "println('First HTTP Request Plugin Status: '+ response.getStatus())\n" - + "println('First HTTP Request Plugin Response: '+ response.getContent())\n" + + "println('" + (withReentrability ? "First " : "") + "HTTP Request Plugin Status: '+ response.getStatus())\n" + + "println('" + (withReentrability ? "First " : "") + "First HTTP Request Plugin Response: '+ response.getContent())\n" + "\n" - + "echo \"Querying HTTPS with credential again (reentrability)...\"\n" - + "response = httpRequest(url: 'https://github.xcom/api/v3',\n" - + " httpMode: 'GET',\n" - + " authentication: authentication,\n" - + " consoleLogResponseBody: true,\n" - + " contentType : 'APPLICATION_FORM',\n" - + " validResponseCodes: '100:599',\n" - + " quiet: false)\n" - + "println('Second HTTP Request Plugin Status: '+ response.getStatus())\n" - + "println('Second HTTP Request Plugin Response: '+ response.getContent())\n" - + "\n"; + + (withReentrability ? ( + "msg = \"Querying HTTPS with credential again (reentrability)...\"\n" + + "echo msg;" + (verbosePipelines ? " System.out.println(msg); System.err.println(msg)" : "" ) + ";\n" + + "response = httpRequest(url: 'https://github.xcom/api/v3',\n" + + " httpMode: 'GET',\n" + + " authentication: authentication,\n" + + " consoleLogResponseBody: true,\n" + + " contentType : 'APPLICATION_FORM',\n" + + " validResponseCodes: '100:599',\n" + + " quiet: false)\n" + + "println('Second HTTP Request Plugin Status: '+ response.getStatus())\n" + + "println('Second HTTP Request Plugin Response: '+ response.getContent())\n" + + "\n" ) + : "" ); } + /** Wrapper for {@link #cpsScriptCredentialTestHttpRequest(String, String, Boolean, Boolean)} + * to MAYBE trace {@code withLocalCertLookup=verbosePipelines} by default */ + private String cpsScriptCredentialTestHttpRequest(String id, String runnerTag, Boolean withReentrability) { + return cpsScriptCredentialTestHttpRequest(id, runnerTag, withReentrability, verbosePipelines); + } + + /** Wrapper for {@link #cpsScriptCredentialTestHttpRequest(String, String, Boolean, Boolean)} + * to MAYBE trace {@code withLocalCertLookup=verbosePipelines} + * and enable {@code withReentrability=true} by default */ + private String cpsScriptCredentialTestHttpRequest(String id, String runnerTag) { + return cpsScriptCredentialTestHttpRequest(id, runnerTag, true, verbosePipelines); + } + + /** Wrapper for {@link #cpsScriptCredentialTestHttpRequest(String, String, Boolean, Boolean)} + * to use a certificate credential {@code id="myCert"} and trace {@code withLocalCertLookup=true} */ + private String cpsScriptCertCredentialTestHttpRequest(String runnerTag) { + return cpsScriptCredentialTestHttpRequest("myCert", runnerTag, false, true); + } + + ///////////////////////////////////////////////////////////////// + // Certificate credentials retrievability by http-request-plugin + // in a local JVM (should work with all versions of credentials plugin) + ///////////////////////////////////////////////////////////////// + + // A set of tests with certificate credentials in different contexts + // NOTE: Test cases on remote agent require the PR + // https://github.com/jenkinsci/credentials-plugin/pull/391 + // to be merged first, so credentials-plugin processes snapshot() + // and readable keystore data gets to remote agent. + // Note that the tests below focus on ability of the plugin to load and + // process the key store specified by the credential, rather than that + // it is usable further. It would be a separate effort to mock up a web + // server protected by HTTPS and using certificates for login (possibly + // user and server backed by two different CA's), and query that. + + /** Check that "simple" Certificate credentials are usable with pipeline script + * running without a {@code node{}} block. + */ @Test @Issue({"JENKINS-70000", "JENKINS-70101"}) void testCertSimpleHttpRequestOnController() throws Exception { - // Check that credentials are usable with pipeline script - // running without a node{} StandardCredentials credential = getCertificateCredentialSimple(); store.addCredentials(Domain.global(), credential); // Configure the build to use the credential WorkflowJob proj = j.jenkins.createProject(WorkflowJob.class, "proj"); String script = - cpsScriptCredentialTestHttpRequest("cred_cert_simple", "CONTROLLER BUILT-IN"); + cpsScriptCredentialTestImports() + + cpsScriptCredentialTestHttpRequest("cred_cert_simple", "CONTROLLER BUILT-IN", true, true); proj.setDefinition(new CpsFlowDefinition(script, false)); // Execute the build @@ -232,19 +443,21 @@ void testCertSimpleHttpRequestOnController() throws Exception { j.assertLogContains("Treating UnknownHostException", run); } + /** Check that "simple" Certificate credentials are usable with pipeline script + * running on a {@code node{}} (provided by the controller JVM). + */ @Test @Issue({"JENKINS-70000", "JENKINS-70101"}) void testCertSimpleHttpRequestOnNodeLocal() throws Exception { - // Check that credentials are usable with pipeline script - // running on a node{} (provided by the controller) StandardCredentials credential = getCertificateCredentialSimple(); store.addCredentials(Domain.global(), credential); // Configure the build to use the credential WorkflowJob proj = j.jenkins.createProject(WorkflowJob.class, "proj"); String script = + cpsScriptCredentialTestImports() + "node {\n" + - cpsScriptCredentialTestHttpRequest("cred_cert_simple", "CONTROLLER NODE") + + cpsScriptCredentialTestHttpRequest("cred_cert_simple", "CONTROLLER NODE", true, true) + "}\n"; proj.setDefinition(new CpsFlowDefinition(script, false)); @@ -265,18 +478,20 @@ void testCertSimpleHttpRequestOnNodeLocal() throws Exception { j.assertLogContains("Treating UnknownHostException", run); } + /** Check that "trusted" Certificate credentials are usable with pipeline script + * running without a {@code node{}} block. + */ @Test @Issue({"JENKINS-70000", "JENKINS-70101"}) void testCertTrustedHttpRequestOnController() throws Exception { - // Check that credentials are usable with pipeline script - // running without a node{} StandardCredentials credential = getCertificateCredentialTrusted(); store.addCredentials(Domain.global(), credential); // Configure the build to use the credential WorkflowJob proj = j.jenkins.createProject(WorkflowJob.class, "proj"); String script = - cpsScriptCredentialTestHttpRequest("cred_cert_with_ca", "CONTROLLER BUILT-IN"); + cpsScriptCredentialTestImports() + + cpsScriptCredentialTestHttpRequest("cred_cert_with_ca", "CONTROLLER BUILT-IN", true, true); proj.setDefinition(new CpsFlowDefinition(script, false)); // Execute the build @@ -293,19 +508,21 @@ void testCertTrustedHttpRequestOnController() throws Exception { j.assertLogContains("Treating UnknownHostException", run); } + /** Check that "trusted" Certificate credentials are usable with pipeline script + * running on a {@code node{}} (provided by the controller JVM). + */ @Test @Issue({"JENKINS-70000", "JENKINS-70101"}) void testCertTrustedHttpRequestOnNodeLocal() throws Exception { - // Check that credentials are usable with pipeline script - // running on a node{} (provided by the controller) StandardCredentials credential = getCertificateCredentialTrusted(); store.addCredentials(Domain.global(), credential); // Configure the build to use the credential WorkflowJob proj = j.jenkins.createProject(WorkflowJob.class, "proj"); String script = + cpsScriptCredentialTestImports() + "node {\n" + - cpsScriptCredentialTestHttpRequest("cred_cert_with_ca", "CONTROLLER NODE") + + cpsScriptCredentialTestHttpRequest("cred_cert_with_ca", "CONTROLLER NODE", true, true) + "}\n"; proj.setDefinition(new CpsFlowDefinition(script, false)); @@ -323,4 +540,273 @@ void testCertTrustedHttpRequestOnNodeLocal() throws Exception { j.assertLogContains("Treating UnknownHostException", run); } + ///////////////////////////////////////////////////////////////// + // Helpers for pipeline tests with remote agents + ///////////////////////////////////////////////////////////////// + + private Boolean isAvailableAgent() { + // Can be used to skip optional tests if we know we could not set up an agent + if (agent == null) + return false; + return agentUsable; + } + + private Boolean setupAgent() throws OutOfMemoryError, Exception { + if (isAvailableAgent()) + return true; + + // See how credentialsPluginTestRemoteAlways is determined above + // and revise if the ultimately merged fix that started as + // https://github.com/jenkinsci/credentials-plugin/pull/391 + // gets changed before the merge or later on... + 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"; + if (!credentialsPluginTestRemoteAlways) + assumeTrue(credentialsPluginDoesSnapshotsRight, msg_70101); + + // else: credentialsPluginTestRemoteAlways, even if we fail + if (!credentialsPluginDoesSnapshotsRight) { + System.err.println("WARNING: " + msg_70101 + "; this test run was configured to try remote agents anyway"); + // return false; + } + + // Note we anticipate this might fail e.g. due to system resources; + // it should not block the whole test suite from running + // (we would just dynamically skip certain test cases) + try { + // Define a "Permanent Agent" + Label agentLabel = Label.get(agentLabelString); + agent = j.createOnlineSlave(agentLabel); + agent.setNodeDescription("Worker in another JVM, remoting used"); + agent.setNumExecutors(1); + agent.setMode(Node.Mode.EXCLUSIVE); + ///agent.setRetentionStrategy(new RetentionStrategy.Always()); + +/* + // Add node envvars + List env = new ArrayList(); + env.add(new Entry("key1","value1")); + env.add(new Entry("key2","value2")); + EnvironmentVariablesNodeProperty envPro = new EnvironmentVariablesNodeProperty(env); + agent.getNodeProperties().add(envPro); +*/ + + String agentLog = null; + agentUsable = false; + for (long i = 0; i < 5; i++) { + Thread.sleep(1000); + agentLog = agent.getComputer().getLog(); + if (i == 2 && (agentLog == null || agentLog.isEmpty())) { + // Give it a little time to autostart, then kick it up if needed: + agent.getComputer().connect(true); // "always" should have started it; avoid duplicate runs + } + if (agentLog != null && agentLog.contains("Agent successfully connected and online")) { + agentUsable = true; + break; + } + } + System.out.println("Spawned build agent " + + "usability: " + agentUsable.toString() + + "; connection log:" + (agentLog == null ? " " : "\n" + agentLog)); + } catch (Descriptor.FormException | NullPointerException e) { + agentUsable = false; + } + + return agentUsable; + } + + ///////////////////////////////////////////////////////////////// + // Certificate credentials retrievability by http-request-plugin + // in a set of local+remote JVMs (should work with versions of + // credentials plugin where issue JENKINS-70101 is fixed) + ///////////////////////////////////////////////////////////////// + + /** Simplified version of simple/trusted tests with "myCert" credential id, + * transplanted from https://github.com/jenkinsci/credentials-plugin/pull/391 : + * Check that Certificate credentials are usable with pipeline script + * running without a {@code node{}} block. + */ + @Test + @Issue("JENKINS-70101") + void testCertHttpRequestOnController() throws Exception { + prepareUploadedKeystore(); + + // Configure the build to use the credential + WorkflowJob proj = j.jenkins.createProject(WorkflowJob.class, "proj"); + String script = + cpsScriptCredentialTestImports() + + cpsScriptCertCredentialTestHttpRequest("CONTROLLER BUILT-IN"); + proj.setDefinition(new CpsFlowDefinition(script, false)); + + // Execute the build + WorkflowRun run = proj.scheduleBuild2(0).get(); + if (verbosePipelines) System.out.println(getLogAsStringPlaintext(run)); + + // Check expectations + j.assertBuildStatus(Result.SUCCESS, run); + // Got to the end? + j.assertLogContains("HTTP Request Plugin Response: ", run); + j.assertLogContains("Using authentication: myCert", run); + } + + /** Simplified version of simple/trusted tests with "myCert" credential id, + * transplanted from https://github.com/jenkinsci/credentials-plugin/pull/391 : + * Check that Certificate credentials are usable with pipeline script + * running on a {@code node{}} (provided by the controller) + */ + @Test + @Issue("JENKINS-70101") + void testCertHttpRequestOnNodeLocal() throws Exception { + prepareUploadedKeystore(); + + // Configure the build to use the credential + WorkflowJob proj = j.jenkins.createProject(WorkflowJob.class, "proj"); + String script = + cpsScriptCredentialTestImports() + + "node {\n" + + cpsScriptCertCredentialTestHttpRequest("CONTROLLER NODE") + + "}\n"; + proj.setDefinition(new CpsFlowDefinition(script, false)); + + // Execute the build + WorkflowRun run = proj.scheduleBuild2(0).get(); + if (verbosePipelines) System.out.println(getLogAsStringPlaintext(run)); + + // Check expectations + j.assertBuildStatus(Result.SUCCESS, run); + // Got to the end? + j.assertLogContains("HTTP Request Plugin Response: ", run); + j.assertLogContains("Using authentication: myCert", run); + } + + /** + * Check that Certificate credentials are usable with pipeline script + * running on a remote {@code node{}} with separate JVM (e.g. + * check that remoting and credential snapshot work properly). + */ + @Test + @Issue("JENKINS-70101") + void testCertHttpRequestOnNodeRemote() throws Exception { + assumeTrue(this.setupAgent() == true, "This test needs a separate build agent"); + + prepareUploadedKeystore(); + + // Configure the build to use the credential + WorkflowJob proj = j.jenkins.createProject(WorkflowJob.class, "proj"); + String script = + cpsScriptCredentialTestImports() + + "node(\"" + agentLabelString + "\") {\n" + + cpsScriptCertCredentialTestHttpRequest("REMOTE NODE") + + "}\n"; + proj.setDefinition(new CpsFlowDefinition(script, false)); + + // Execute the build + WorkflowRun run = proj.scheduleBuild2(0).get(); + if (verbosePipelines) System.out.println(getLogAsStringPlaintext(run)); + + // Check expectations + j.assertBuildStatus(Result.SUCCESS, run); + // Got to the end? + j.assertLogContains("HTTP Request Plugin Response: ", run); + } + + ///////////////////////////////////////////////////////////////// + // User/pass credentials tests + ///////////////////////////////////////////////////////////////// + + // Partially from UsernamePasswordCredentialsImplTest setup() + private void prepareUsernamePassword() throws IOException, FormException { + UsernamePasswordCredentialsImpl credentials = + new UsernamePasswordCredentialsImpl(null, + "abc123", "Bob’s laptop", + "bob", "s3cr3t"); + SystemCredentialsProvider.getInstance().getCredentials().add(credentials); + SystemCredentialsProvider.getInstance().save(); + } + + private String cpsScriptUsernamePasswordCredentialTestHttpRequest(String runnerTag) { + return cpsScriptCredentialTestHttpRequest("abc123", runnerTag, false); + } + + /** Check that Username credentials are usable with pipeline script + * running without a {@code node{}} block. + */ + @Test + @Issue("JENKINS-70101") + void testUsernamePasswordHttpRequestOnController() throws Exception { + prepareUsernamePassword(); + + // Configure the build to use the credential + WorkflowJob proj = j.jenkins.createProject(WorkflowJob.class, "proj"); + String script = + cpsScriptUsernamePasswordCredentialTestHttpRequest("CONTROLLER BUILT-IN"); + proj.setDefinition(new CpsFlowDefinition(script, false)); + + // Execute the build + WorkflowRun run = proj.scheduleBuild2(0).get(); + if (verbosePipelines) System.out.println(getLogAsStringPlaintext(run)); + + // Check expectations + j.assertBuildStatus(Result.SUCCESS, run); + // Got to the end? + j.assertLogContains("HTTP Request Plugin Response: ", run); + } + + /** Check that Username credentials are usable with pipeline script + * running on a {@code node{}} (provided by the controller JVM). + */ + @Test + @Issue("JENKINS-70101") + void testUsernamePasswordHttpRequestOnNodeLocal() throws Exception { + prepareUsernamePassword(); + + // Configure the build to use the credential + WorkflowJob proj = j.jenkins.createProject(WorkflowJob.class, "proj"); + String script = + "node {\n" + + cpsScriptUsernamePasswordCredentialTestHttpRequest("CONTROLLER NODE") + + "}\n"; + proj.setDefinition(new CpsFlowDefinition(script, false)); + + // Execute the build + WorkflowRun run = proj.scheduleBuild2(0).get(); + if (verbosePipelines) System.out.println(getLogAsStringPlaintext(run)); + + // Check expectations + j.assertBuildStatus(Result.SUCCESS, run); + // Got to the end? + j.assertLogContains("HTTP Request Plugin Response: ", run); + } + + /** + * Check that Username credentials are usable with pipeline script + * running on a remote {@code node{}} with separate JVM (e.g. + * check that remoting and credential snapshot work properly). + */ + @Test + @Issue("JENKINS-70101") + void testUsernamePasswordHttpRequestOnNodeRemote() throws Exception { + // Check that credentials are usable with pipeline script + // running on a remote node{} with separate JVM (check + // that remoting/snapshot work properly) + assumeTrue(this.setupAgent() == true, "This test needs a separate build agent"); + + prepareUsernamePassword(); + + // Configure the build to use the credential + WorkflowJob proj = j.jenkins.createProject(WorkflowJob.class, "proj"); + String script = + "node(\"" + agentLabelString + "\") {\n" + + cpsScriptUsernamePasswordCredentialTestHttpRequest("REMOTE NODE") + + "}\n"; + proj.setDefinition(new CpsFlowDefinition(script, false)); + + // Execute the build + WorkflowRun run = proj.scheduleBuild2(0).get(); + if (verbosePipelines) System.out.println(getLogAsStringPlaintext(run)); + + // Check expectations + j.assertBuildStatus(Result.SUCCESS, run); + // Got to the end? + j.assertLogContains("HTTP Request Plugin Response: ", run); + } }