From 14b4424d1a7558108198c0784c1d26185736c359 Mon Sep 17 00:00:00 2001 From: Evgeny Klimov Date: Sat, 19 Nov 2022 15:43:37 +0100 Subject: [PATCH 01/21] CertificateCredentialsImplTest: add tests for local/remote Jenkins nodes [JENKINS-70101] --- pom.xml | 18 ++ .../impl/CertificateCredentialsImplTest.java | 290 ++++++++++++++++++ 2 files changed, 308 insertions(+) diff --git a/pom.xml b/pom.xml index b73040a64..215e860ef 100644 --- a/pom.xml +++ b/pom.xml @@ -161,6 +161,24 @@ workflow-basic-steps test + + org.jenkins-ci.plugins + job-dsl + 1.81 + test + + + org.jenkins-ci.plugins + command-launcher + 1.6 + test + + + org.jenkins-ci.plugins + script-security + 1218.v39ca_7f7ed0a_c + test + diff --git a/src/test/java/com/cloudbees/plugins/credentials/impl/CertificateCredentialsImplTest.java b/src/test/java/com/cloudbees/plugins/credentials/impl/CertificateCredentialsImplTest.java index 78b095bde..833b0b348 100644 --- a/src/test/java/com/cloudbees/plugins/credentials/impl/CertificateCredentialsImplTest.java +++ b/src/test/java/com/cloudbees/plugins/credentials/impl/CertificateCredentialsImplTest.java @@ -51,12 +51,29 @@ import hudson.Util; import hudson.cli.CLICommandInvoker; import hudson.cli.UpdateJobCommand; +import hudson.model.Descriptor; import hudson.model.ItemGroup; import hudson.model.Job; +import hudson.model.Node; +import hudson.model.Result; +import hudson.model.Slave; import hudson.security.ACL; +import hudson.slaves.CommandLauncher; +import hudson.slaves.ComputerLauncher; +import hudson.slaves.DumbSlave; +import hudson.slaves.RetentionStrategy; import hudson.util.Secret; +import jenkins.model.GlobalConfiguration; import jenkins.model.Jenkins; import org.apache.commons.io.FileUtils; +import org.jenkinsci.plugins.scriptsecurity.scripts.ScriptApproval; +import org.jenkinsci.plugins.scriptsecurity.scripts.languages.GroovyLanguage; +import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition; +import org.jenkinsci.plugins.workflow.job.WorkflowJob; +import org.jenkinsci.plugins.workflow.job.WorkflowRun; + +import javaposse.jobdsl.plugin.GlobalJobDslSecurityConfiguration; + import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -64,8 +81,10 @@ import org.jvnet.hudson.test.Issue; import org.jvnet.hudson.test.JenkinsRule; import org.jvnet.hudson.test.recipes.LocalData; +import org.kohsuke.stapler.StaplerRequest; import java.io.File; +import java.io.FileOutputStream; import java.io.IOException; import java.net.URL; import java.net.URLEncoder; @@ -81,6 +100,7 @@ import static org.hamcrest.Matchers.hasSize; import static org.junit.Assert.*; import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assume.assumeThat; public class CertificateCredentialsImplTest { @@ -96,6 +116,18 @@ public class CertificateCredentialsImplTest { private static final String INVALID_PASSWORD = "blabla"; private static final String EXPECTED_DISPLAY_NAME = "EMAILADDRESS=me@myhost.mydomain, CN=pkcs12, O=Fort-Funston, L=SanFrancisco, ST=CA, C=US"; + // See setupAgent() below + @Rule + public TemporaryFolder tmpAgent = new TemporaryFolder(); + @Rule + public TemporaryFolder tmpWorker = new TemporaryFolder(); + // Where did we save that file?.. + private File agentJar = null; + // Can this be reused for many test cases? + private Slave agent = null; + // Unknown/started/not usable + private Boolean agentUsable = null; + @Before public void setup() throws IOException { p12 = tmp.newFile("test.p12"); @@ -106,6 +138,99 @@ public void setup() throws IOException { r.jenkins.setCrumbIssuer(null); } + // Helpers for some of the test cases (initially tied to JENKINS-70101 research) + // TODO: Offload to some class many tests can call upon? + Boolean isAvailableAgent() { + // Can be used to skip optional tests if we know we could not set up an agent + if (agentJar == null) + return false; + if (agent == null) + return false; + return agentUsable; + } + + Boolean setupAgent() throws IOException, InterruptedException, OutOfMemoryError { + // Note we anticipate this might fail; it should not block the whole test suite from running + // Loosely inspired by + // https://docs.cloudbees.com/docs/cloudbees-ci-kb/latest/client-and-managed-masters/create-agent-node-from-groovy + + // Is it known-impossible to start the agent? + if (agentUsable != null && agentUsable == false) + return agentUsable; // quickly for re-runs + + // Did we download this file for earlier test cases? + if (agentJar == null) { + try { + URL url = new URL(r.jenkins.getRootUrl() + "jnlpJars/agent.jar"); + agentJar = tmpAgent.newFile("agent.jar"); + FileOutputStream out = new FileOutputStream(agentJar); + out.write(url.openStream().readAllBytes()); + out.close(); + } catch (IOException | OutOfMemoryError e) { + agentJar = null; + agentUsable = false; + + System.out.println("Failed to download agent.jar from test instance: " + + e.toString()); + + return agentUsable; + } + } + + // This CLI spelling and quoting should play well with both Windows + // (including spaces in directory names) and Unix/Linux + ComputerLauncher launcher = new CommandLauncher( + "\"" + System.getProperty("java.home") + File.separator + "bin" + + File.separator + "java\" -jar \"" + agentJar.getAbsolutePath().toString() + "\"" + ); + + try { + // Define a "Permanent Agent" + agent = new DumbSlave( + "worker", + tmpWorker.getRoot().getAbsolutePath().toString(), + launcher); + agent.setNodeDescription("Worker in another JVM, remoting used"); + agent.setNumExecutors(1); + agent.setLabelString("worker"); + 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); +*/ + + r.jenkins.addNode(agent); + + 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; + } + @Test public void displayName() throws IOException { SecretBytes uploadedKeystore = SecretBytes.fromBytes(Files.readAllBytes(p12.toPath())); @@ -397,4 +522,169 @@ private CredentialsStore getFolderStore(Folder f) { return folderStore; } + // Helper for a few tests below + // Roughly follows what tests above were proven to succeed doing + private void prepareUploadedKeystore() throws IOException { + prepareUploadedKeystore("myCert", "password"); + } + + private void prepareUploadedKeystore(String id, String password) throws IOException { + SecretBytes uploadedKeystore = SecretBytes.fromBytes(Files.readAllBytes(p12.toPath())); + CertificateCredentialsImpl.UploadedKeyStoreSource storeSource = new CertificateCredentialsImpl.UploadedKeyStoreSource(uploadedKeystore); + CertificateCredentialsImpl credentials = new CertificateCredentialsImpl(null, id, null, password, storeSource); + SystemCredentialsProvider.getInstance().getCredentials().add(credentials); + SystemCredentialsProvider.getInstance().save(); + } + + 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"; + } + + String cpsScriptCredentialTest(String runnerTag) { + return cpsScriptCredentialTest("myCert", "password", runnerTag); + } + + String cpsScriptCredentialTest(String id, String password, String runnerTag) { + return "def authentication='" + id + "';\n" + + "def password='" + password + "';\n" + + "StandardCredentials credential = CredentialsMatchers.firstOrNull(\n" + + " CredentialsProvider.lookupCredentials(\n" + + " StandardCredentials.class,\n" + + " Jenkins.instance, null, null),\n" + + " CredentialsMatchers.withId(authentication));\n" + + "StandardCredentials credentialSnap = CredentialsProvider.snapshot(credential);\n\n" + + "\n" + + "echo \"CRED ON " + runnerTag + ":\"\n" + + "echo credential.toString()\n" + + "KeyStore keyStore = credential.getKeyStore();\n" + + "KeyStoreSource kss = ((CertificateCredentialsImpl) credential).getKeyStoreSource();\n" + + "echo \"KSS: \" + kss.toString()\n" + + "byte[] kssb = kss.getKeyStoreBytes();\n" + + "echo \"KSS bytes (len): \" + kssb.length\n" + + "\n" + + "echo \"CRED-SNAP ON " + runnerTag + ":\"\n" + + "echo credentialSnap.toString()\n" + + "KeyStore keyStoreSnap = credentialSnap.getKeyStore();\n" + + "KeyStoreSource kssSnap = ((CertificateCredentialsImpl) credentialSnap).getKeyStoreSource();\n" + + "echo \"KSS-SNAP: \" + kssSnap.toString()\n" + + "byte[] kssbSnap = kssSnap.getKeyStoreBytes();\n" + + "echo \"KSS-SNAP bytes (len): \" + kssbSnap.length\n" + + "\n"; + } + + private void relaxScriptSecurityScript(String script) throws IOException { + ScriptApproval.get().preapprove(script, GroovyLanguage.get()); + for (ScriptApproval.PendingScript p : ScriptApproval.get().getPendingScripts()) { + ScriptApproval.get().approveScript(p.getHash()); + } + } + + private void relaxScriptSecurityGlobal() throws IOException { + StaplerRequest stapler = null; + net.sf.json.JSONObject jsonObject = new net.sf.json.JSONObject(); + jsonObject.put("useScriptSecurity", false); + GlobalConfiguration.all().get(GlobalJobDslSecurityConfiguration.class).configure(stapler, jsonObject); + GlobalConfiguration.all().get(GlobalJobDslSecurityConfiguration.class).save(); +/* + GlobalConfiguration.all().get(GlobalJobDslSecurityConfiguration.class).useScriptSecurity=false; + GlobalConfiguration.all().get(GlobalJobDslSecurityConfiguration.class).save(); + */ + } + + private void relaxScriptSecurityCredentialTestSignatures() throws IOException { + ScriptApproval.get().approveSignature("method com.cloudbees.plugins.credentials.common.CertificateCredentials getKeyStore"); + ScriptApproval.get().approveSignature("method com.cloudbees.plugins.credentials.impl.CertificateCredentialsImpl getKeyStoreSource"); + ScriptApproval.get().approveSignature("method com.cloudbees.plugins.credentials.impl.CertificateCredentialsImpl$KeyStoreSource getKeyStoreBytes"); + ScriptApproval.get().approveSignature("staticMethod com.cloudbees.plugins.credentials.CredentialsMatchers firstOrNull java.lang.Iterable com.cloudbees.plugins.credentials.CredentialsMatcher"); + ScriptApproval.get().approveSignature("staticMethod com.cloudbees.plugins.credentials.CredentialsMatchers withId java.lang.String"); + ScriptApproval.get().approveSignature("staticMethod com.cloudbees.plugins.credentials.CredentialsProvider lookupCredentials java.lang.Class hudson.model.ItemGroup org.acegisecurity.Authentication java.util.List"); + ScriptApproval.get().approveSignature("staticMethod com.cloudbees.plugins.credentials.CredentialsProvider snapshot com.cloudbees.plugins.credentials.Credentials"); + ScriptApproval.get().approveSignature("staticMethod jenkins.model.Jenkins getInstance"); + } + + @Test + @Issue("JENKINS-70101") + public void keyStoreReadableOnController() throws Exception { + // Check that credentials are usable with pipeline script + // running without a node{} + prepareUploadedKeystore(); + + // Configure the build to use the credential + WorkflowJob proj = r.jenkins.createProject(WorkflowJob.class, "proj"); + String script = cpsScriptCredentialTestImports() + + cpsScriptCredentialTest("CONTROLLER BUILT-IN"); + proj.setDefinition(new CpsFlowDefinition(script, true)); + relaxScriptSecurityCredentialTestSignatures(); + + // Execute the build + WorkflowRun run = proj.scheduleBuild2(0).get(); + + // Check expectations + r.assertBuildStatus(Result.SUCCESS, run); + // Got to the end? + r.assertLogContains("KSS-SNAP bytes", run); + } + + @Test + @Issue("JENKINS-70101") + public void keyStoreReadableOnNodeLocal() throws Exception { + // Check that credentials are usable with pipeline script + // running on a node{} (provided by the controller) + prepareUploadedKeystore(); + + // Configure the build to use the credential + WorkflowJob proj = r.jenkins.createProject(WorkflowJob.class, "proj"); + String script = cpsScriptCredentialTestImports() + + "node {\n" + + cpsScriptCredentialTest("CONTROLLER NODE") + + "}\n"; + proj.setDefinition(new CpsFlowDefinition(script, true)); + relaxScriptSecurityCredentialTestSignatures(); + + // Execute the build + WorkflowRun run = proj.scheduleBuild2(0).get(); + + // Check expectations + r.assertBuildStatus(Result.SUCCESS, run); + // Got to the end? + r.assertLogContains("KSS-SNAP bytes", run); + } + + @Test + @Issue("JENKINS-70101") + public void keyStoreReadableOnNodeRemote() throws Exception { + // Check that credentials are usable with pipeline script + // running on a remote node{} with separate JVM (check + // that remoting/snapshot work properly) + assumeThat("This test needs a separate build agent", this.setupAgent(), is(true)); + + prepareUploadedKeystore(); + + // Configure the build to use the credential + WorkflowJob proj = r.jenkins.createProject(WorkflowJob.class, "proj"); + String script = cpsScriptCredentialTestImports() + + "node(\"worker\") {\n" + + cpsScriptCredentialTest("REMOTE NODE") + + "}\n"; + proj.setDefinition(new CpsFlowDefinition(script, true)); + relaxScriptSecurityCredentialTestSignatures(); + + // Execute the build + WorkflowRun run = proj.scheduleBuild2(0).get(); + + // Check expectations + r.assertBuildStatus(Result.SUCCESS, run); + // Got to the end? + r.assertLogContains("KSS-SNAP bytes", run); + } } From 666186a35ecb9968856fb6441e498a452495aea4 Mon Sep 17 00:00:00 2001 From: Evgeny Klimov Date: Sun, 20 Nov 2022 12:53:38 +0100 Subject: [PATCH 02/21] CertificateCredentialsImplTest: simplify relaxation of pipeline script security --- pom.xml | 6 --- .../impl/CertificateCredentialsImplTest.java | 45 ++----------------- 2 files changed, 3 insertions(+), 48 deletions(-) diff --git a/pom.xml b/pom.xml index 215e860ef..5a790d309 100644 --- a/pom.xml +++ b/pom.xml @@ -173,12 +173,6 @@ 1.6 test - - org.jenkins-ci.plugins - script-security - 1218.v39ca_7f7ed0a_c - test - diff --git a/src/test/java/com/cloudbees/plugins/credentials/impl/CertificateCredentialsImplTest.java b/src/test/java/com/cloudbees/plugins/credentials/impl/CertificateCredentialsImplTest.java index 833b0b348..119ab5416 100644 --- a/src/test/java/com/cloudbees/plugins/credentials/impl/CertificateCredentialsImplTest.java +++ b/src/test/java/com/cloudbees/plugins/credentials/impl/CertificateCredentialsImplTest.java @@ -63,17 +63,12 @@ import hudson.slaves.DumbSlave; import hudson.slaves.RetentionStrategy; import hudson.util.Secret; -import jenkins.model.GlobalConfiguration; import jenkins.model.Jenkins; import org.apache.commons.io.FileUtils; -import org.jenkinsci.plugins.scriptsecurity.scripts.ScriptApproval; -import org.jenkinsci.plugins.scriptsecurity.scripts.languages.GroovyLanguage; import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition; import org.jenkinsci.plugins.workflow.job.WorkflowJob; import org.jenkinsci.plugins.workflow.job.WorkflowRun; -import javaposse.jobdsl.plugin.GlobalJobDslSecurityConfiguration; - import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -81,7 +76,6 @@ import org.jvnet.hudson.test.Issue; import org.jvnet.hudson.test.JenkinsRule; import org.jvnet.hudson.test.recipes.LocalData; -import org.kohsuke.stapler.StaplerRequest; import java.io.File; import java.io.FileOutputStream; @@ -582,36 +576,6 @@ String cpsScriptCredentialTest(String id, String password, String runnerTag) { "\n"; } - private void relaxScriptSecurityScript(String script) throws IOException { - ScriptApproval.get().preapprove(script, GroovyLanguage.get()); - for (ScriptApproval.PendingScript p : ScriptApproval.get().getPendingScripts()) { - ScriptApproval.get().approveScript(p.getHash()); - } - } - - private void relaxScriptSecurityGlobal() throws IOException { - StaplerRequest stapler = null; - net.sf.json.JSONObject jsonObject = new net.sf.json.JSONObject(); - jsonObject.put("useScriptSecurity", false); - GlobalConfiguration.all().get(GlobalJobDslSecurityConfiguration.class).configure(stapler, jsonObject); - GlobalConfiguration.all().get(GlobalJobDslSecurityConfiguration.class).save(); -/* - GlobalConfiguration.all().get(GlobalJobDslSecurityConfiguration.class).useScriptSecurity=false; - GlobalConfiguration.all().get(GlobalJobDslSecurityConfiguration.class).save(); - */ - } - - private void relaxScriptSecurityCredentialTestSignatures() throws IOException { - ScriptApproval.get().approveSignature("method com.cloudbees.plugins.credentials.common.CertificateCredentials getKeyStore"); - ScriptApproval.get().approveSignature("method com.cloudbees.plugins.credentials.impl.CertificateCredentialsImpl getKeyStoreSource"); - ScriptApproval.get().approveSignature("method com.cloudbees.plugins.credentials.impl.CertificateCredentialsImpl$KeyStoreSource getKeyStoreBytes"); - ScriptApproval.get().approveSignature("staticMethod com.cloudbees.plugins.credentials.CredentialsMatchers firstOrNull java.lang.Iterable com.cloudbees.plugins.credentials.CredentialsMatcher"); - ScriptApproval.get().approveSignature("staticMethod com.cloudbees.plugins.credentials.CredentialsMatchers withId java.lang.String"); - ScriptApproval.get().approveSignature("staticMethod com.cloudbees.plugins.credentials.CredentialsProvider lookupCredentials java.lang.Class hudson.model.ItemGroup org.acegisecurity.Authentication java.util.List"); - ScriptApproval.get().approveSignature("staticMethod com.cloudbees.plugins.credentials.CredentialsProvider snapshot com.cloudbees.plugins.credentials.Credentials"); - ScriptApproval.get().approveSignature("staticMethod jenkins.model.Jenkins getInstance"); - } - @Test @Issue("JENKINS-70101") public void keyStoreReadableOnController() throws Exception { @@ -623,8 +587,7 @@ public void keyStoreReadableOnController() throws Exception { WorkflowJob proj = r.jenkins.createProject(WorkflowJob.class, "proj"); String script = cpsScriptCredentialTestImports() + cpsScriptCredentialTest("CONTROLLER BUILT-IN"); - proj.setDefinition(new CpsFlowDefinition(script, true)); - relaxScriptSecurityCredentialTestSignatures(); + proj.setDefinition(new CpsFlowDefinition(script, false)); // Execute the build WorkflowRun run = proj.scheduleBuild2(0).get(); @@ -648,8 +611,7 @@ public void keyStoreReadableOnNodeLocal() throws Exception { "node {\n" + cpsScriptCredentialTest("CONTROLLER NODE") + "}\n"; - proj.setDefinition(new CpsFlowDefinition(script, true)); - relaxScriptSecurityCredentialTestSignatures(); + proj.setDefinition(new CpsFlowDefinition(script, false)); // Execute the build WorkflowRun run = proj.scheduleBuild2(0).get(); @@ -676,8 +638,7 @@ public void keyStoreReadableOnNodeRemote() throws Exception { "node(\"worker\") {\n" + cpsScriptCredentialTest("REMOTE NODE") + "}\n"; - proj.setDefinition(new CpsFlowDefinition(script, true)); - relaxScriptSecurityCredentialTestSignatures(); + proj.setDefinition(new CpsFlowDefinition(script, false)); // Execute the build WorkflowRun run = proj.scheduleBuild2(0).get(); From 58c4e2682469a4d9170af2ffe8e1774da52e3bc6 Mon Sep 17 00:00:00 2001 From: Evgeny Klimov Date: Sun, 20 Nov 2022 13:54:35 +0100 Subject: [PATCH 03/21] CertificateCredentialsImplTest: add withCredentials() tests --- pom.xml | 6 +++++ .../impl/CertificateCredentialsImplTest.java | 24 +++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/pom.xml b/pom.xml index 5a790d309..0ea2a9c2c 100644 --- a/pom.xml +++ b/pom.xml @@ -173,6 +173,12 @@ 1.6 test + + org.jenkins-ci.plugins + credentials-binding + 523.vd859a_4b_122e6 + test + diff --git a/src/test/java/com/cloudbees/plugins/credentials/impl/CertificateCredentialsImplTest.java b/src/test/java/com/cloudbees/plugins/credentials/impl/CertificateCredentialsImplTest.java index 119ab5416..7b0b19c58 100644 --- a/src/test/java/com/cloudbees/plugins/credentials/impl/CertificateCredentialsImplTest.java +++ b/src/test/java/com/cloudbees/plugins/credentials/impl/CertificateCredentialsImplTest.java @@ -541,6 +541,15 @@ String cpsScriptCredentialTestImports() { "import com.cloudbees.plugins.credentials.impl.CertificateCredentialsImpl.KeyStoreSource;\n" + "import hudson.security.ACL;\n" + "import java.security.KeyStore;\n" + + "\n" + + "@NonCPS\n" + + "def getKey(def keystoreName, def keystoreFormat, def keyPassword, def alias) {\n" + + " def p12file = new FileInputStream(keystoreName)\n" + + " def keystore = KeyStore.getInstance(keystoreFormat)\n" + + " keystore.load(p12file, keyPassword.toCharArray())\n" + + " def key = keystore.getKey(alias, keyPassword.toCharArray())\n" + + " return key.getEncoded().encodeBase64().toString()\n" + + "}\n" + "\n"; } @@ -573,6 +582,21 @@ String cpsScriptCredentialTest(String id, String password, String runnerTag) { "echo \"KSS-SNAP: \" + kssSnap.toString()\n" + "byte[] kssbSnap = kssSnap.getKeyStoreBytes();\n" + "echo \"KSS-SNAP bytes (len): \" + kssbSnap.length\n" + + "\n" + + "echo \"WITH-CREDENTIAL ON " + runnerTag + ":\"\n" + // https://groups.google.com/g/jenkinsci-users/c/evyx0O3bMWE + "withCredentials([certificate(\n" + + " credentialsId: authentication,\n" + + " keystoreVariable: 'keystoreName',\n" + + " passwordVariable: 'keyPassword',\n" + + " aliasVariable: 'myKeyAlias')\n" + + "]) {\n" + + " echo \"Keystore bytes (len): \" + (new File(keystoreName)).length()\n" + + " def keystoreFormat = \"PKCS12\"\n" + + " def keyValue = '' //getKeyValue(keystoreName, keystoreFormat, keyPassword, myKeyAlias)\n" + + " println \"-----BEGIN PRIVATE KEY-----\"\n" + + " println keyValue\n" + + " println \"-----END PRIVATE KEY-----\"\n" + + "}\n" + "\n"; } From f4afd9f0a6aaa6b8d3519269729cd2b5076c21fd Mon Sep 17 00:00:00 2001 From: Evgeny Klimov Date: Sun, 20 Nov 2022 14:15:21 +0100 Subject: [PATCH 04/21] Move multi-agent pipeline tests to dedicated CredentialsInPipelineTest source --- .../CredentialsInPipelineTest.java | 392 ++++++++++++++++++ .../impl/CertificateCredentialsImplTest.java | 275 ------------ 2 files changed, 392 insertions(+), 275 deletions(-) create mode 100644 src/test/java/com/cloudbees/plugins/credentials/CredentialsInPipelineTest.java diff --git a/src/test/java/com/cloudbees/plugins/credentials/CredentialsInPipelineTest.java b/src/test/java/com/cloudbees/plugins/credentials/CredentialsInPipelineTest.java new file mode 100644 index 000000000..7585bb333 --- /dev/null +++ b/src/test/java/com/cloudbees/plugins/credentials/CredentialsInPipelineTest.java @@ -0,0 +1,392 @@ +/* + * The MIT License + * + * Copyright 2022 Jim Klimov. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.cloudbees.plugins.credentials; + +import com.cloudbees.hudson.plugins.folder.Folder; +import com.cloudbees.hudson.plugins.folder.properties.FolderCredentialsProvider; +import com.cloudbees.plugins.credentials.Credentials; +import com.cloudbees.plugins.credentials.CredentialsNameProvider; +import com.cloudbees.plugins.credentials.CredentialsProvider; +import com.cloudbees.plugins.credentials.CredentialsStore; +import com.cloudbees.plugins.credentials.SecretBytes; +import com.cloudbees.plugins.credentials.SystemCredentialsProvider; +import com.cloudbees.plugins.credentials.common.CertificateCredentials; +import com.cloudbees.plugins.credentials.common.StandardCertificateCredentials; +import com.cloudbees.plugins.credentials.domains.Domain; +import com.cloudbees.plugins.credentials.impl.CertificateCredentialsImpl; +import com.cloudbees.plugins.credentials.impl.CertificateCredentialsImplTest; +import com.gargoylesoftware.htmlunit.FormEncodingType; +import com.gargoylesoftware.htmlunit.HttpMethod; +import com.gargoylesoftware.htmlunit.Page; +import com.gargoylesoftware.htmlunit.WebRequest; +import com.gargoylesoftware.htmlunit.html.DomNode; +import com.gargoylesoftware.htmlunit.html.DomNodeList; +import com.gargoylesoftware.htmlunit.html.HtmlElementUtil; +import com.gargoylesoftware.htmlunit.html.HtmlFileInput; +import com.gargoylesoftware.htmlunit.html.HtmlForm; +import com.gargoylesoftware.htmlunit.html.HtmlOption; +import com.gargoylesoftware.htmlunit.html.HtmlPage; +import com.gargoylesoftware.htmlunit.html.HtmlRadioButtonInput; +import hudson.FilePath; +import hudson.Util; +import hudson.cli.CLICommandInvoker; +import hudson.cli.UpdateJobCommand; +import hudson.model.Descriptor; +import hudson.model.ItemGroup; +import hudson.model.Job; +import hudson.model.Node; +import hudson.model.Result; +import hudson.model.Slave; +import hudson.security.ACL; +import hudson.slaves.CommandLauncher; +import hudson.slaves.ComputerLauncher; +import hudson.slaves.DumbSlave; +import hudson.slaves.RetentionStrategy; +import hudson.util.Secret; +import jenkins.model.Jenkins; +import org.apache.commons.io.FileUtils; +import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition; +import org.jenkinsci.plugins.workflow.job.WorkflowJob; +import org.jenkinsci.plugins.workflow.job.WorkflowRun; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.jvnet.hudson.test.Issue; +import org.jvnet.hudson.test.JenkinsRule; +import org.jvnet.hudson.test.recipes.LocalData; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.net.URL; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.Base64; +import java.util.Collections; +import java.util.List; + +import static hudson.cli.CLICommandInvoker.Matcher.failedWith; +import static hudson.cli.CLICommandInvoker.Matcher.succeeded; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasSize; +import static org.junit.Assert.*; +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assume.assumeThat; + +public class CredentialsInPipelineTest { + /** + * The CredentialsInPipelineTest 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. + */ + + @Rule + public JenkinsRule r = new JenkinsRule(); + + // Data for build agent setup + @Rule + public TemporaryFolder tmpAgent = new TemporaryFolder(); + @Rule + public TemporaryFolder tmpWorker = new TemporaryFolder(); + // Where did we save that file?.. + private File agentJar = null; + // Can this be reused for many test cases? + private Slave agent = null; + // Unknown/started/not usable + private Boolean agentUsable = null; + + // From CertificateCredentialImplTest + @Rule + public TemporaryFolder tmp = new TemporaryFolder(); + private File p12; + + @Before + public void setup() { + r.jenkins.setCrumbIssuer(null); + } + + Boolean isAvailableAgent() { + // Can be used to skip optional tests if we know we could not set up an agent + if (agentJar == null) + return false; + if (agent == null) + return false; + return agentUsable; + } + + Boolean setupAgent() throws IOException, InterruptedException, OutOfMemoryError { + // Note we anticipate this might fail; it should not block the whole test suite from running + // Loosely inspired by + // https://docs.cloudbees.com/docs/cloudbees-ci-kb/latest/client-and-managed-masters/create-agent-node-from-groovy + + // Is it known-impossible to start the agent? + if (agentUsable != null && agentUsable == false) + return agentUsable; // quickly for re-runs + + // Did we download this file for earlier test cases? + if (agentJar == null) { + try { + URL url = new URL(r.jenkins.getRootUrl() + "jnlpJars/agent.jar"); + agentJar = tmpAgent.newFile("agent.jar"); + FileOutputStream out = new FileOutputStream(agentJar); + out.write(url.openStream().readAllBytes()); + out.close(); + } catch (IOException | OutOfMemoryError e) { + agentJar = null; + agentUsable = false; + + System.out.println("Failed to download agent.jar from test instance: " + + e.toString()); + + return agentUsable; + } + } + + // This CLI spelling and quoting should play well with both Windows + // (including spaces in directory names) and Unix/Linux + ComputerLauncher launcher = new CommandLauncher( + "\"" + System.getProperty("java.home") + File.separator + "bin" + + File.separator + "java\" -jar \"" + agentJar.getAbsolutePath().toString() + "\"" + ); + + try { + // Define a "Permanent Agent" + agent = new DumbSlave( + "worker", + tmpWorker.getRoot().getAbsolutePath().toString(), + launcher); + agent.setNodeDescription("Worker in another JVM, remoting used"); + agent.setNumExecutors(1); + agent.setLabelString("worker"); + 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); +*/ + + r.jenkins.addNode(agent); + + 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 tests + ///////////////////////////////////////////////////////////////// + + // Partially from CertificateCredentialImplTest setup() + private void prepareUploadedKeystore() throws IOException { + prepareUploadedKeystore("myCert", "password"); + } + + private void prepareUploadedKeystore(String id, String password) throws IOException { + if (p12 == null) { + p12 = tmp.newFile("test.p12"); + FileUtils.copyURLToFile(CertificateCredentialsImplTest.class.getResource("test.p12"), p12); + } + + SecretBytes uploadedKeystore = SecretBytes.fromBytes(Files.readAllBytes(p12.toPath())); + CertificateCredentialsImpl.UploadedKeyStoreSource storeSource = new CertificateCredentialsImpl.UploadedKeyStoreSource(uploadedKeystore); + CertificateCredentialsImpl credentials = new CertificateCredentialsImpl(null, id, null, password, storeSource); + SystemCredentialsProvider.getInstance().getCredentials().add(credentials); + SystemCredentialsProvider.getInstance().save(); + } + + 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" + + "@NonCPS\n" + + "def getKey(def keystoreName, def keystoreFormat, def keyPassword, def alias) {\n" + + " def p12file = new FileInputStream(keystoreName)\n" + + " def keystore = KeyStore.getInstance(keystoreFormat)\n" + + " keystore.load(p12file, keyPassword.toCharArray())\n" + + " def key = keystore.getKey(alias, keyPassword.toCharArray())\n" + + " return key.getEncoded().encodeBase64().toString()\n" + + "}\n" + + "\n"; + } + + String cpsScriptCredentialTest(String runnerTag) { + return cpsScriptCredentialTest("myCert", "password", runnerTag); + } + + String cpsScriptCredentialTest(String id, String password, String runnerTag) { + return "def authentication='" + id + "';\n" + + "def password='" + password + "';\n" + + "StandardCredentials credential = CredentialsMatchers.firstOrNull(\n" + + " CredentialsProvider.lookupCredentials(\n" + + " StandardCredentials.class,\n" + + " Jenkins.instance, null, null),\n" + + " CredentialsMatchers.withId(authentication));\n" + + "StandardCredentials credentialSnap = CredentialsProvider.snapshot(credential);\n\n" + + "\n" + + "echo \"CRED ON " + runnerTag + ":\"\n" + + "echo credential.toString()\n" + + "KeyStore keyStore = credential.getKeyStore();\n" + + "KeyStoreSource kss = ((CertificateCredentialsImpl) credential).getKeyStoreSource();\n" + + "echo \"KSS: \" + kss.toString()\n" + + "byte[] kssb = kss.getKeyStoreBytes();\n" + + "echo \"KSS bytes (len): \" + kssb.length\n" + + "\n" + + "echo \"CRED-SNAP ON " + runnerTag + ":\"\n" + + "echo credentialSnap.toString()\n" + + "KeyStore keyStoreSnap = credentialSnap.getKeyStore();\n" + + "KeyStoreSource kssSnap = ((CertificateCredentialsImpl) credentialSnap).getKeyStoreSource();\n" + + "echo \"KSS-SNAP: \" + kssSnap.toString()\n" + + "byte[] kssbSnap = kssSnap.getKeyStoreBytes();\n" + + "echo \"KSS-SNAP bytes (len): \" + kssbSnap.length\n" + + "\n" + + "echo \"WITH-CREDENTIAL ON " + runnerTag + ":\"\n" + // https://groups.google.com/g/jenkinsci-users/c/evyx0O3bMWE + "withCredentials([certificate(\n" + + " credentialsId: authentication,\n" + + " keystoreVariable: 'keystoreName',\n" + + " passwordVariable: 'keyPassword',\n" + + " aliasVariable: 'myKeyAlias')\n" + + "]) {\n" + + " echo \"Keystore bytes (len): \" + (new File(keystoreName)).length()\n" + + " def keystoreFormat = \"PKCS12\"\n" + + " def keyValue = '' //getKeyValue(keystoreName, keystoreFormat, keyPassword, myKeyAlias)\n" + + " println \"-----BEGIN PRIVATE KEY-----\"\n" + + " println keyValue\n" + + " println \"-----END PRIVATE KEY-----\"\n" + + "}\n" + + "\n"; + } + + @Test + @Issue("JENKINS-70101") + public void keyStoreReadableOnController() throws Exception { + // Check that credentials are usable with pipeline script + // running without a node{} + prepareUploadedKeystore(); + + // Configure the build to use the credential + WorkflowJob proj = r.jenkins.createProject(WorkflowJob.class, "proj"); + String script = cpsScriptCredentialTestImports() + + cpsScriptCredentialTest("CONTROLLER BUILT-IN"); + proj.setDefinition(new CpsFlowDefinition(script, false)); + + // Execute the build + WorkflowRun run = proj.scheduleBuild2(0).get(); + + // Check expectations + r.assertBuildStatus(Result.SUCCESS, run); + // Got to the end? + r.assertLogContains("KSS-SNAP bytes", run); + } + + @Test + @Issue("JENKINS-70101") + public void keyStoreReadableOnNodeLocal() throws Exception { + // Check that credentials are usable with pipeline script + // running on a node{} (provided by the controller) + prepareUploadedKeystore(); + + // Configure the build to use the credential + WorkflowJob proj = r.jenkins.createProject(WorkflowJob.class, "proj"); + String script = cpsScriptCredentialTestImports() + + "node {\n" + + cpsScriptCredentialTest("CONTROLLER NODE") + + "}\n"; + proj.setDefinition(new CpsFlowDefinition(script, false)); + + // Execute the build + WorkflowRun run = proj.scheduleBuild2(0).get(); + + // Check expectations + r.assertBuildStatus(Result.SUCCESS, run); + // Got to the end? + r.assertLogContains("KSS-SNAP bytes", run); + } + + @Test + @Issue("JENKINS-70101") + public void keyStoreReadableOnNodeRemote() throws Exception { + // Check that credentials are usable with pipeline script + // running on a remote node{} with separate JVM (check + // that remoting/snapshot work properly) + assumeThat("This test needs a separate build agent", this.setupAgent(), is(true)); + + prepareUploadedKeystore(); + + // Configure the build to use the credential + WorkflowJob proj = r.jenkins.createProject(WorkflowJob.class, "proj"); + String script = cpsScriptCredentialTestImports() + + "node(\"worker\") {\n" + + cpsScriptCredentialTest("REMOTE NODE") + + "}\n"; + proj.setDefinition(new CpsFlowDefinition(script, false)); + + // Execute the build + WorkflowRun run = proj.scheduleBuild2(0).get(); + + // Check expectations + r.assertBuildStatus(Result.SUCCESS, run); + // Got to the end? + r.assertLogContains("KSS-SNAP bytes", run); + } + +} diff --git a/src/test/java/com/cloudbees/plugins/credentials/impl/CertificateCredentialsImplTest.java b/src/test/java/com/cloudbees/plugins/credentials/impl/CertificateCredentialsImplTest.java index 7b0b19c58..78b095bde 100644 --- a/src/test/java/com/cloudbees/plugins/credentials/impl/CertificateCredentialsImplTest.java +++ b/src/test/java/com/cloudbees/plugins/credentials/impl/CertificateCredentialsImplTest.java @@ -51,24 +51,12 @@ import hudson.Util; import hudson.cli.CLICommandInvoker; import hudson.cli.UpdateJobCommand; -import hudson.model.Descriptor; import hudson.model.ItemGroup; import hudson.model.Job; -import hudson.model.Node; -import hudson.model.Result; -import hudson.model.Slave; import hudson.security.ACL; -import hudson.slaves.CommandLauncher; -import hudson.slaves.ComputerLauncher; -import hudson.slaves.DumbSlave; -import hudson.slaves.RetentionStrategy; import hudson.util.Secret; import jenkins.model.Jenkins; import org.apache.commons.io.FileUtils; -import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition; -import org.jenkinsci.plugins.workflow.job.WorkflowJob; -import org.jenkinsci.plugins.workflow.job.WorkflowRun; - import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -78,7 +66,6 @@ import org.jvnet.hudson.test.recipes.LocalData; import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; import java.net.URL; import java.net.URLEncoder; @@ -94,7 +81,6 @@ import static org.hamcrest.Matchers.hasSize; import static org.junit.Assert.*; import static org.hamcrest.CoreMatchers.*; -import static org.junit.Assume.assumeThat; public class CertificateCredentialsImplTest { @@ -110,18 +96,6 @@ public class CertificateCredentialsImplTest { private static final String INVALID_PASSWORD = "blabla"; private static final String EXPECTED_DISPLAY_NAME = "EMAILADDRESS=me@myhost.mydomain, CN=pkcs12, O=Fort-Funston, L=SanFrancisco, ST=CA, C=US"; - // See setupAgent() below - @Rule - public TemporaryFolder tmpAgent = new TemporaryFolder(); - @Rule - public TemporaryFolder tmpWorker = new TemporaryFolder(); - // Where did we save that file?.. - private File agentJar = null; - // Can this be reused for many test cases? - private Slave agent = null; - // Unknown/started/not usable - private Boolean agentUsable = null; - @Before public void setup() throws IOException { p12 = tmp.newFile("test.p12"); @@ -132,99 +106,6 @@ public void setup() throws IOException { r.jenkins.setCrumbIssuer(null); } - // Helpers for some of the test cases (initially tied to JENKINS-70101 research) - // TODO: Offload to some class many tests can call upon? - Boolean isAvailableAgent() { - // Can be used to skip optional tests if we know we could not set up an agent - if (agentJar == null) - return false; - if (agent == null) - return false; - return agentUsable; - } - - Boolean setupAgent() throws IOException, InterruptedException, OutOfMemoryError { - // Note we anticipate this might fail; it should not block the whole test suite from running - // Loosely inspired by - // https://docs.cloudbees.com/docs/cloudbees-ci-kb/latest/client-and-managed-masters/create-agent-node-from-groovy - - // Is it known-impossible to start the agent? - if (agentUsable != null && agentUsable == false) - return agentUsable; // quickly for re-runs - - // Did we download this file for earlier test cases? - if (agentJar == null) { - try { - URL url = new URL(r.jenkins.getRootUrl() + "jnlpJars/agent.jar"); - agentJar = tmpAgent.newFile("agent.jar"); - FileOutputStream out = new FileOutputStream(agentJar); - out.write(url.openStream().readAllBytes()); - out.close(); - } catch (IOException | OutOfMemoryError e) { - agentJar = null; - agentUsable = false; - - System.out.println("Failed to download agent.jar from test instance: " + - e.toString()); - - return agentUsable; - } - } - - // This CLI spelling and quoting should play well with both Windows - // (including spaces in directory names) and Unix/Linux - ComputerLauncher launcher = new CommandLauncher( - "\"" + System.getProperty("java.home") + File.separator + "bin" + - File.separator + "java\" -jar \"" + agentJar.getAbsolutePath().toString() + "\"" - ); - - try { - // Define a "Permanent Agent" - agent = new DumbSlave( - "worker", - tmpWorker.getRoot().getAbsolutePath().toString(), - launcher); - agent.setNodeDescription("Worker in another JVM, remoting used"); - agent.setNumExecutors(1); - agent.setLabelString("worker"); - 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); -*/ - - r.jenkins.addNode(agent); - - 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; - } - @Test public void displayName() throws IOException { SecretBytes uploadedKeystore = SecretBytes.fromBytes(Files.readAllBytes(p12.toPath())); @@ -516,160 +397,4 @@ private CredentialsStore getFolderStore(Folder f) { return folderStore; } - // Helper for a few tests below - // Roughly follows what tests above were proven to succeed doing - private void prepareUploadedKeystore() throws IOException { - prepareUploadedKeystore("myCert", "password"); - } - - private void prepareUploadedKeystore(String id, String password) throws IOException { - SecretBytes uploadedKeystore = SecretBytes.fromBytes(Files.readAllBytes(p12.toPath())); - CertificateCredentialsImpl.UploadedKeyStoreSource storeSource = new CertificateCredentialsImpl.UploadedKeyStoreSource(uploadedKeystore); - CertificateCredentialsImpl credentials = new CertificateCredentialsImpl(null, id, null, password, storeSource); - SystemCredentialsProvider.getInstance().getCredentials().add(credentials); - SystemCredentialsProvider.getInstance().save(); - } - - 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" + - "@NonCPS\n" + - "def getKey(def keystoreName, def keystoreFormat, def keyPassword, def alias) {\n" + - " def p12file = new FileInputStream(keystoreName)\n" + - " def keystore = KeyStore.getInstance(keystoreFormat)\n" + - " keystore.load(p12file, keyPassword.toCharArray())\n" + - " def key = keystore.getKey(alias, keyPassword.toCharArray())\n" + - " return key.getEncoded().encodeBase64().toString()\n" + - "}\n" + - "\n"; - } - - String cpsScriptCredentialTest(String runnerTag) { - return cpsScriptCredentialTest("myCert", "password", runnerTag); - } - - String cpsScriptCredentialTest(String id, String password, String runnerTag) { - return "def authentication='" + id + "';\n" + - "def password='" + password + "';\n" + - "StandardCredentials credential = CredentialsMatchers.firstOrNull(\n" + - " CredentialsProvider.lookupCredentials(\n" + - " StandardCredentials.class,\n" + - " Jenkins.instance, null, null),\n" + - " CredentialsMatchers.withId(authentication));\n" + - "StandardCredentials credentialSnap = CredentialsProvider.snapshot(credential);\n\n" + - "\n" + - "echo \"CRED ON " + runnerTag + ":\"\n" + - "echo credential.toString()\n" + - "KeyStore keyStore = credential.getKeyStore();\n" + - "KeyStoreSource kss = ((CertificateCredentialsImpl) credential).getKeyStoreSource();\n" + - "echo \"KSS: \" + kss.toString()\n" + - "byte[] kssb = kss.getKeyStoreBytes();\n" + - "echo \"KSS bytes (len): \" + kssb.length\n" + - "\n" + - "echo \"CRED-SNAP ON " + runnerTag + ":\"\n" + - "echo credentialSnap.toString()\n" + - "KeyStore keyStoreSnap = credentialSnap.getKeyStore();\n" + - "KeyStoreSource kssSnap = ((CertificateCredentialsImpl) credentialSnap).getKeyStoreSource();\n" + - "echo \"KSS-SNAP: \" + kssSnap.toString()\n" + - "byte[] kssbSnap = kssSnap.getKeyStoreBytes();\n" + - "echo \"KSS-SNAP bytes (len): \" + kssbSnap.length\n" + - "\n" + - "echo \"WITH-CREDENTIAL ON " + runnerTag + ":\"\n" + // https://groups.google.com/g/jenkinsci-users/c/evyx0O3bMWE - "withCredentials([certificate(\n" + - " credentialsId: authentication,\n" + - " keystoreVariable: 'keystoreName',\n" + - " passwordVariable: 'keyPassword',\n" + - " aliasVariable: 'myKeyAlias')\n" + - "]) {\n" + - " echo \"Keystore bytes (len): \" + (new File(keystoreName)).length()\n" + - " def keystoreFormat = \"PKCS12\"\n" + - " def keyValue = '' //getKeyValue(keystoreName, keystoreFormat, keyPassword, myKeyAlias)\n" + - " println \"-----BEGIN PRIVATE KEY-----\"\n" + - " println keyValue\n" + - " println \"-----END PRIVATE KEY-----\"\n" + - "}\n" + - "\n"; - } - - @Test - @Issue("JENKINS-70101") - public void keyStoreReadableOnController() throws Exception { - // Check that credentials are usable with pipeline script - // running without a node{} - prepareUploadedKeystore(); - - // Configure the build to use the credential - WorkflowJob proj = r.jenkins.createProject(WorkflowJob.class, "proj"); - String script = cpsScriptCredentialTestImports() + - cpsScriptCredentialTest("CONTROLLER BUILT-IN"); - proj.setDefinition(new CpsFlowDefinition(script, false)); - - // Execute the build - WorkflowRun run = proj.scheduleBuild2(0).get(); - - // Check expectations - r.assertBuildStatus(Result.SUCCESS, run); - // Got to the end? - r.assertLogContains("KSS-SNAP bytes", run); - } - - @Test - @Issue("JENKINS-70101") - public void keyStoreReadableOnNodeLocal() throws Exception { - // Check that credentials are usable with pipeline script - // running on a node{} (provided by the controller) - prepareUploadedKeystore(); - - // Configure the build to use the credential - WorkflowJob proj = r.jenkins.createProject(WorkflowJob.class, "proj"); - String script = cpsScriptCredentialTestImports() + - "node {\n" + - cpsScriptCredentialTest("CONTROLLER NODE") + - "}\n"; - proj.setDefinition(new CpsFlowDefinition(script, false)); - - // Execute the build - WorkflowRun run = proj.scheduleBuild2(0).get(); - - // Check expectations - r.assertBuildStatus(Result.SUCCESS, run); - // Got to the end? - r.assertLogContains("KSS-SNAP bytes", run); - } - - @Test - @Issue("JENKINS-70101") - public void keyStoreReadableOnNodeRemote() throws Exception { - // Check that credentials are usable with pipeline script - // running on a remote node{} with separate JVM (check - // that remoting/snapshot work properly) - assumeThat("This test needs a separate build agent", this.setupAgent(), is(true)); - - prepareUploadedKeystore(); - - // Configure the build to use the credential - WorkflowJob proj = r.jenkins.createProject(WorkflowJob.class, "proj"); - String script = cpsScriptCredentialTestImports() + - "node(\"worker\") {\n" + - cpsScriptCredentialTest("REMOTE NODE") + - "}\n"; - proj.setDefinition(new CpsFlowDefinition(script, false)); - - // Execute the build - WorkflowRun run = proj.scheduleBuild2(0).get(); - - // Check expectations - r.assertBuildStatus(Result.SUCCESS, run); - // Got to the end? - r.assertLogContains("KSS-SNAP bytes", run); - } } From f4896f908554d7eb8b646eedf12cc73e86cc31c1 Mon Sep 17 00:00:00 2001 From: Evgeny Klimov Date: Sun, 20 Nov 2022 14:25:13 +0100 Subject: [PATCH 05/21] CredentialsInPipelineTest: separate cpsScriptCredentialTestWithCredentials() et al to standalone cases --- .../CredentialsInPipelineTest.java | 144 +++++++++++++++--- 1 file changed, 123 insertions(+), 21 deletions(-) diff --git a/src/test/java/com/cloudbees/plugins/credentials/CredentialsInPipelineTest.java b/src/test/java/com/cloudbees/plugins/credentials/CredentialsInPipelineTest.java index 7585bb333..756ca7989 100644 --- a/src/test/java/com/cloudbees/plugins/credentials/CredentialsInPipelineTest.java +++ b/src/test/java/com/cloudbees/plugins/credentials/CredentialsInPipelineTest.java @@ -72,6 +72,7 @@ import org.jenkinsci.plugins.workflow.job.WorkflowRun; import org.junit.Before; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; @@ -235,6 +236,8 @@ private void prepareUploadedKeystore() throws IOException { private void prepareUploadedKeystore(String id, String password) throws IOException { if (p12 == null) { + // Contains a private key + openvpn certs, + // as alias named "1" (according to keytool) p12 = tmp.newFile("test.p12"); FileUtils.copyURLToFile(CertificateCredentialsImplTest.class.getResource("test.p12"), p12); } @@ -257,18 +260,13 @@ String cpsScriptCredentialTestImports() { "import com.cloudbees.plugins.credentials.impl.CertificateCredentialsImpl.KeyStoreSource;\n" + "import hudson.security.ACL;\n" + "import java.security.KeyStore;\n" + - "\n" + - "@NonCPS\n" + - "def getKey(def keystoreName, def keystoreFormat, def keyPassword, def alias) {\n" + - " def p12file = new FileInputStream(keystoreName)\n" + - " def keystore = KeyStore.getInstance(keystoreFormat)\n" + - " keystore.load(p12file, keyPassword.toCharArray())\n" + - " def key = keystore.getKey(alias, keyPassword.toCharArray())\n" + - " return key.getEncoded().encodeBase64().toString()\n" + - "}\n" + "\n"; } + ///////////////////////////////////////////////////////////////// + // Certificate credentials retrievability in (trusted) pipeline + ///////////////////////////////////////////////////////////////// + String cpsScriptCredentialTest(String runnerTag) { return cpsScriptCredentialTest("myCert", "password", runnerTag); } @@ -298,8 +296,107 @@ String cpsScriptCredentialTest(String id, String password, String runnerTag) { "echo \"KSS-SNAP: \" + kssSnap.toString()\n" + "byte[] kssbSnap = kssSnap.getKeyStoreBytes();\n" + "echo \"KSS-SNAP bytes (len): \" + kssbSnap.length\n" + - "\n" + - "echo \"WITH-CREDENTIAL ON " + runnerTag + ":\"\n" + // https://groups.google.com/g/jenkinsci-users/c/evyx0O3bMWE + "\n"; + } + + @Test + @Issue("JENKINS-70101") + public void testCertKeyStoreReadableOnController() throws Exception { + // Check that credentials are usable with pipeline script + // running without a node{} + prepareUploadedKeystore(); + + // Configure the build to use the credential + WorkflowJob proj = r.jenkins.createProject(WorkflowJob.class, "proj"); + String script = cpsScriptCredentialTestImports() + + cpsScriptCredentialTest("CONTROLLER BUILT-IN"); + proj.setDefinition(new CpsFlowDefinition(script, false)); + + // Execute the build + WorkflowRun run = proj.scheduleBuild2(0).get(); + + // Check expectations + r.assertBuildStatus(Result.SUCCESS, run); + // Got to the end? + r.assertLogContains("KSS-SNAP bytes", run); + } + + @Test + @Issue("JENKINS-70101") + public void testCertKeyStoreReadableOnNodeLocal() throws Exception { + // Check that credentials are usable with pipeline script + // running on a node{} (provided by the controller) + prepareUploadedKeystore(); + + // Configure the build to use the credential + WorkflowJob proj = r.jenkins.createProject(WorkflowJob.class, "proj"); + String script = cpsScriptCredentialTestImports() + + "node {\n" + + cpsScriptCredentialTest("CONTROLLER NODE") + + "}\n"; + proj.setDefinition(new CpsFlowDefinition(script, false)); + + // Execute the build + WorkflowRun run = proj.scheduleBuild2(0).get(); + + // Check expectations + r.assertBuildStatus(Result.SUCCESS, run); + // Got to the end? + r.assertLogContains("KSS-SNAP bytes", run); + } + + @Test + @Issue("JENKINS-70101") + public void testCertKeyStoreReadableOnNodeRemote() throws Exception { + // Check that credentials are usable with pipeline script + // running on a remote node{} with separate JVM (check + // that remoting/snapshot work properly) + assumeThat("This test needs a separate build agent", this.setupAgent(), is(true)); + + prepareUploadedKeystore(); + + // Configure the build to use the credential + WorkflowJob proj = r.jenkins.createProject(WorkflowJob.class, "proj"); + String script = cpsScriptCredentialTestImports() + + "node(\"worker\") {\n" + + cpsScriptCredentialTest("REMOTE NODE") + + "}\n"; + proj.setDefinition(new CpsFlowDefinition(script, false)); + + // Execute the build + WorkflowRun run = proj.scheduleBuild2(0).get(); + + // Check expectations + r.assertBuildStatus(Result.SUCCESS, run); + // Got to the end? + r.assertLogContains("KSS-SNAP bytes", run); + } + + ///////////////////////////////////////////////////////////////// + // Certificate credentials retrievability by withCredentials() step + ///////////////////////////////////////////////////////////////// + + String cpsScriptCredentialTestGetKeyValue() { + return "@NonCPS\n" + + "def getKeyValue(def keystoreName, def keystoreFormat, def keyPassword, def alias) {\n" + + " def p12file = new FileInputStream(keystoreName)\n" + + " def keystore = KeyStore.getInstance(keystoreFormat)\n" + + " keystore.load(p12file, keyPassword.toCharArray())\n" + + " p12file.close()\n" + + " def key = keystore.getKey(alias ? alias : \"1\", keyPassword.toCharArray())\n" + + " return key.getEncoded().encodeBase64().toString()\n" + + "}\n" + + "\n"; + } + + String cpsScriptCredentialTestWithCredentials(String runnerTag) { + return cpsScriptCredentialTestWithCredentials("myCert", "password", runnerTag); + } + + String cpsScriptCredentialTestWithCredentials(String id, String password, String runnerTag) { + return "def authentication='" + id + "';\n" + + "def password='" + password + "';\n" + + "echo \"WITH-CREDENTIALS ON " + runnerTag + ":\"\n" + "withCredentials([certificate(\n" + " credentialsId: authentication,\n" + " keystoreVariable: 'keystoreName',\n" + @@ -307,8 +404,9 @@ String cpsScriptCredentialTest(String id, String password, String runnerTag) { " aliasVariable: 'myKeyAlias')\n" + "]) {\n" + " echo \"Keystore bytes (len): \" + (new File(keystoreName)).length()\n" + + " echo \"Got expected password? ${keyPassword == password}\"\n" + " def keystoreFormat = \"PKCS12\"\n" + - " def keyValue = '' //getKeyValue(keystoreName, keystoreFormat, keyPassword, myKeyAlias)\n" + + " def keyValue = getKeyValue(keystoreName, keystoreFormat, keyPassword, env?.myKeyAlias)\n" + " println \"-----BEGIN PRIVATE KEY-----\"\n" + " println keyValue\n" + " println \"-----END PRIVATE KEY-----\"\n" + @@ -317,8 +415,9 @@ String cpsScriptCredentialTest(String id, String password, String runnerTag) { } @Test + @Ignore("Work with keystore file requires a node") @Issue("JENKINS-70101") - public void keyStoreReadableOnController() throws Exception { + public void testCertWithCredentialsOnController() throws Exception { // Check that credentials are usable with pipeline script // running without a node{} prepareUploadedKeystore(); @@ -326,7 +425,8 @@ public void keyStoreReadableOnController() throws Exception { // Configure the build to use the credential WorkflowJob proj = r.jenkins.createProject(WorkflowJob.class, "proj"); String script = cpsScriptCredentialTestImports() + - cpsScriptCredentialTest("CONTROLLER BUILT-IN"); + cpsScriptCredentialTestGetKeyValue() + + cpsScriptCredentialTestWithCredentials("CONTROLLER BUILT-IN"); proj.setDefinition(new CpsFlowDefinition(script, false)); // Execute the build @@ -335,12 +435,12 @@ public void keyStoreReadableOnController() throws Exception { // Check expectations r.assertBuildStatus(Result.SUCCESS, run); // Got to the end? - r.assertLogContains("KSS-SNAP bytes", run); + r.assertLogContains("END PRIVATE KEY", run); } @Test @Issue("JENKINS-70101") - public void keyStoreReadableOnNodeLocal() throws Exception { + public void testCertWithCredentialsOnNodeLocal() throws Exception { // Check that credentials are usable with pipeline script // running on a node{} (provided by the controller) prepareUploadedKeystore(); @@ -348,8 +448,9 @@ public void keyStoreReadableOnNodeLocal() throws Exception { // Configure the build to use the credential WorkflowJob proj = r.jenkins.createProject(WorkflowJob.class, "proj"); String script = cpsScriptCredentialTestImports() + + cpsScriptCredentialTestGetKeyValue() + "node {\n" + - cpsScriptCredentialTest("CONTROLLER NODE") + + cpsScriptCredentialTestWithCredentials("CONTROLLER NODE") + "}\n"; proj.setDefinition(new CpsFlowDefinition(script, false)); @@ -359,12 +460,12 @@ public void keyStoreReadableOnNodeLocal() throws Exception { // Check expectations r.assertBuildStatus(Result.SUCCESS, run); // Got to the end? - r.assertLogContains("KSS-SNAP bytes", run); + r.assertLogContains("END PRIVATE KEY", run); } @Test @Issue("JENKINS-70101") - public void keyStoreReadableOnNodeRemote() throws Exception { + public void testCertWithCredentialsOnNodeRemote() throws Exception { // Check that credentials are usable with pipeline script // running on a remote node{} with separate JVM (check // that remoting/snapshot work properly) @@ -375,8 +476,9 @@ public void keyStoreReadableOnNodeRemote() throws Exception { // Configure the build to use the credential WorkflowJob proj = r.jenkins.createProject(WorkflowJob.class, "proj"); String script = cpsScriptCredentialTestImports() + + cpsScriptCredentialTestGetKeyValue() + "node(\"worker\") {\n" + - cpsScriptCredentialTest("REMOTE NODE") + + cpsScriptCredentialTestWithCredentials("REMOTE NODE") + "}\n"; proj.setDefinition(new CpsFlowDefinition(script, false)); @@ -386,7 +488,7 @@ public void keyStoreReadableOnNodeRemote() throws Exception { // Check expectations r.assertBuildStatus(Result.SUCCESS, run); // Got to the end? - r.assertLogContains("KSS-SNAP bytes", run); + r.assertLogContains("END PRIVATE KEY", run); } } From 3077afa9f67d70d9c76db95d14e6eb951ac0f97b Mon Sep 17 00:00:00 2001 From: Evgeny Klimov Date: Sun, 20 Nov 2022 14:52:48 +0100 Subject: [PATCH 06/21] CredentialsInPipelineTest: print logs of pipeline runs to test output --- .../credentials/CredentialsInPipelineTest.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/test/java/com/cloudbees/plugins/credentials/CredentialsInPipelineTest.java b/src/test/java/com/cloudbees/plugins/credentials/CredentialsInPipelineTest.java index 756ca7989..5d0c11a48 100644 --- a/src/test/java/com/cloudbees/plugins/credentials/CredentialsInPipelineTest.java +++ b/src/test/java/com/cloudbees/plugins/credentials/CredentialsInPipelineTest.java @@ -80,6 +80,7 @@ import org.jvnet.hudson.test.JenkinsRule; import org.jvnet.hudson.test.recipes.LocalData; +import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; @@ -225,6 +226,12 @@ Boolean setupAgent() throws IOException, InterruptedException, OutOfMemoryError return agentUsable; } + String getLogAsStringPlaintext(WorkflowRun f) throws java.io.IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + f.getLogText().writeLogTo(0, baos); + return baos.toString(); + } + ///////////////////////////////////////////////////////////////// // Certificate credentials tests ///////////////////////////////////////////////////////////////// @@ -314,6 +321,7 @@ public void testCertKeyStoreReadableOnController() throws Exception { // Execute the build WorkflowRun run = proj.scheduleBuild2(0).get(); + System.out.println(getLogAsStringPlaintext(run)); // Check expectations r.assertBuildStatus(Result.SUCCESS, run); @@ -338,6 +346,7 @@ public void testCertKeyStoreReadableOnNodeLocal() throws Exception { // Execute the build WorkflowRun run = proj.scheduleBuild2(0).get(); + System.out.println(getLogAsStringPlaintext(run)); // Check expectations r.assertBuildStatus(Result.SUCCESS, run); @@ -365,6 +374,7 @@ public void testCertKeyStoreReadableOnNodeRemote() throws Exception { // Execute the build WorkflowRun run = proj.scheduleBuild2(0).get(); + System.out.println(getLogAsStringPlaintext(run)); // Check expectations r.assertBuildStatus(Result.SUCCESS, run); @@ -431,6 +441,7 @@ public void testCertWithCredentialsOnController() throws Exception { // Execute the build WorkflowRun run = proj.scheduleBuild2(0).get(); + System.out.println(getLogAsStringPlaintext(run)); // Check expectations r.assertBuildStatus(Result.SUCCESS, run); @@ -456,6 +467,7 @@ public void testCertWithCredentialsOnNodeLocal() throws Exception { // Execute the build WorkflowRun run = proj.scheduleBuild2(0).get(); + System.out.println(getLogAsStringPlaintext(run)); // Check expectations r.assertBuildStatus(Result.SUCCESS, run); @@ -484,6 +496,7 @@ public void testCertWithCredentialsOnNodeRemote() throws Exception { // Execute the build WorkflowRun run = proj.scheduleBuild2(0).get(); + System.out.println(getLogAsStringPlaintext(run)); // Check expectations r.assertBuildStatus(Result.SUCCESS, run); From fda8836c7a50c6c51973089fc715cf2943ac77a8 Mon Sep 17 00:00:00 2001 From: Evgeny Klimov Date: Sun, 20 Nov 2022 14:59:11 +0100 Subject: [PATCH 07/21] CredentialsInPipelineTest: refactor alias passing to getKey() --- .../credentials/CredentialsInPipelineTest.java | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/test/java/com/cloudbees/plugins/credentials/CredentialsInPipelineTest.java b/src/test/java/com/cloudbees/plugins/credentials/CredentialsInPipelineTest.java index 5d0c11a48..7fd4bff66 100644 --- a/src/test/java/com/cloudbees/plugins/credentials/CredentialsInPipelineTest.java +++ b/src/test/java/com/cloudbees/plugins/credentials/CredentialsInPipelineTest.java @@ -275,12 +275,13 @@ String cpsScriptCredentialTestImports() { ///////////////////////////////////////////////////////////////// String cpsScriptCredentialTest(String runnerTag) { - return cpsScriptCredentialTest("myCert", "password", runnerTag); + return cpsScriptCredentialTest("myCert", "password", "1", runnerTag); } - String cpsScriptCredentialTest(String id, String password, String runnerTag) { + String cpsScriptCredentialTest(String id, String password, String alias, String runnerTag) { return "def authentication='" + id + "';\n" + "def password='" + password + "';\n" + + "def alias='" + alias + "';\n" + "StandardCredentials credential = CredentialsMatchers.firstOrNull(\n" + " CredentialsProvider.lookupCredentials(\n" + " StandardCredentials.class,\n" + @@ -393,19 +394,21 @@ String cpsScriptCredentialTestGetKeyValue() { " def keystore = KeyStore.getInstance(keystoreFormat)\n" + " keystore.load(p12file, keyPassword.toCharArray())\n" + " p12file.close()\n" + - " def key = keystore.getKey(alias ? alias : \"1\", keyPassword.toCharArray())\n" + + " def key = keystore.getKey(alias, keyPassword.toCharArray())\n" + " return key.getEncoded().encodeBase64().toString()\n" + "}\n" + "\n"; } String cpsScriptCredentialTestWithCredentials(String runnerTag) { - return cpsScriptCredentialTestWithCredentials("myCert", "password", runnerTag); + return cpsScriptCredentialTestWithCredentials("myCert", "password", "1", runnerTag); } - String cpsScriptCredentialTestWithCredentials(String id, String password, String runnerTag) { + String cpsScriptCredentialTestWithCredentials(String id, String password, String alias, String runnerTag) { + // Note: for some reason does not pass (env?.)myKeyAlias to closure return "def authentication='" + id + "';\n" + "def password='" + password + "';\n" + + "def alias='" + alias + "';\n" + "echo \"WITH-CREDENTIALS ON " + runnerTag + ":\"\n" + "withCredentials([certificate(\n" + " credentialsId: authentication,\n" + @@ -416,7 +419,7 @@ String cpsScriptCredentialTestWithCredentials(String id, String password, String " echo \"Keystore bytes (len): \" + (new File(keystoreName)).length()\n" + " echo \"Got expected password? ${keyPassword == password}\"\n" + " def keystoreFormat = \"PKCS12\"\n" + - " def keyValue = getKeyValue(keystoreName, keystoreFormat, keyPassword, env?.myKeyAlias)\n" + + " def keyValue = getKeyValue(keystoreName, keystoreFormat, keyPassword, (env?.myKeyAlias ? env?.myKeyAlias : alias))\n" + " println \"-----BEGIN PRIVATE KEY-----\"\n" + " println keyValue\n" + " println \"-----END PRIVATE KEY-----\"\n" + From f45cdbe7f059d169466082c09746d0c1601691ab Mon Sep 17 00:00:00 2001 From: Evgeny Klimov Date: Sun, 20 Nov 2022 15:04:59 +0100 Subject: [PATCH 08/21] CredentialsInPipelineTest: report private key in testCertKeyStoreReadable*() to make sure it is right --- .../plugins/credentials/CredentialsInPipelineTest.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/test/java/com/cloudbees/plugins/credentials/CredentialsInPipelineTest.java b/src/test/java/com/cloudbees/plugins/credentials/CredentialsInPipelineTest.java index 7fd4bff66..2920d17c7 100644 --- a/src/test/java/com/cloudbees/plugins/credentials/CredentialsInPipelineTest.java +++ b/src/test/java/com/cloudbees/plugins/credentials/CredentialsInPipelineTest.java @@ -296,6 +296,10 @@ String cpsScriptCredentialTest(String id, String password, String alias, String "echo \"KSS: \" + kss.toString()\n" + "byte[] kssb = kss.getKeyStoreBytes();\n" + "echo \"KSS bytes (len): \" + kssb.length\n" + + "String keyValue = keyStore.getKey(alias, password.toCharArray()).getEncoded().encodeBase64().toString()\n" + + "echo \"-----BEGIN PRIVATE KEY-----\"\n" + + "echo keyValue\n" + + "echo \"-----END PRIVATE KEY-----\"\n" + "\n" + "echo \"CRED-SNAP ON " + runnerTag + ":\"\n" + "echo credentialSnap.toString()\n" + @@ -304,6 +308,10 @@ String cpsScriptCredentialTest(String id, String password, String alias, String "echo \"KSS-SNAP: \" + kssSnap.toString()\n" + "byte[] kssbSnap = kssSnap.getKeyStoreBytes();\n" + "echo \"KSS-SNAP bytes (len): \" + kssbSnap.length\n" + + "String keyValueSnap = keyStoreSnap.getKey(alias, password.toCharArray()).getEncoded().encodeBase64().toString()\n" + + "echo \"-----BEGIN PRIVATE KEY-----\"\n" + + "echo keyValueSnap\n" + + "echo \"-----END PRIVATE KEY-----\"\n" + "\n"; } From b5e0ec8473af8a65c3d91b129132680777ef5615 Mon Sep 17 00:00:00 2001 From: Evgeny Klimov Date: Sun, 20 Nov 2022 15:05:13 +0100 Subject: [PATCH 09/21] CredentialsInPipelineTest: reword message so it is not obfuscated --- .../plugins/credentials/CredentialsInPipelineTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/com/cloudbees/plugins/credentials/CredentialsInPipelineTest.java b/src/test/java/com/cloudbees/plugins/credentials/CredentialsInPipelineTest.java index 2920d17c7..2d3dc5c42 100644 --- a/src/test/java/com/cloudbees/plugins/credentials/CredentialsInPipelineTest.java +++ b/src/test/java/com/cloudbees/plugins/credentials/CredentialsInPipelineTest.java @@ -425,7 +425,7 @@ String cpsScriptCredentialTestWithCredentials(String id, String password, String " aliasVariable: 'myKeyAlias')\n" + "]) {\n" + " echo \"Keystore bytes (len): \" + (new File(keystoreName)).length()\n" + - " echo \"Got expected password? ${keyPassword == password}\"\n" + + " echo \"Got expected key pass? ${keyPassword == password}\"\n" + " def keystoreFormat = \"PKCS12\"\n" + " def keyValue = getKeyValue(keystoreName, keystoreFormat, keyPassword, (env?.myKeyAlias ? env?.myKeyAlias : alias))\n" + " println \"-----BEGIN PRIVATE KEY-----\"\n" + From 981355e720881c300f0a9de96be2e2530d321bb1 Mon Sep 17 00:00:00 2001 From: Evgeny Klimov Date: Sun, 20 Nov 2022 15:16:03 +0100 Subject: [PATCH 10/21] CredentialsInPipelineTest: rename "testCert" and "cpsScriptCertCredential" to differentiate from expected other credential types --- .../CredentialsInPipelineTest.java | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/test/java/com/cloudbees/plugins/credentials/CredentialsInPipelineTest.java b/src/test/java/com/cloudbees/plugins/credentials/CredentialsInPipelineTest.java index 2d3dc5c42..6444510a7 100644 --- a/src/test/java/com/cloudbees/plugins/credentials/CredentialsInPipelineTest.java +++ b/src/test/java/com/cloudbees/plugins/credentials/CredentialsInPipelineTest.java @@ -274,11 +274,11 @@ String cpsScriptCredentialTestImports() { // Certificate credentials retrievability in (trusted) pipeline ///////////////////////////////////////////////////////////////// - String cpsScriptCredentialTest(String runnerTag) { - return cpsScriptCredentialTest("myCert", "password", "1", runnerTag); + String cpsScriptCertCredentialTestScriptedPipeline(String runnerTag) { + return cpsScriptCertCredentialTestScriptedPipeline("myCert", "password", "1", runnerTag); } - String cpsScriptCredentialTest(String id, String password, String alias, String runnerTag) { + String cpsScriptCertCredentialTestScriptedPipeline(String id, String password, String alias, String runnerTag) { return "def authentication='" + id + "';\n" + "def password='" + password + "';\n" + "def alias='" + alias + "';\n" + @@ -325,7 +325,7 @@ public void testCertKeyStoreReadableOnController() throws Exception { // Configure the build to use the credential WorkflowJob proj = r.jenkins.createProject(WorkflowJob.class, "proj"); String script = cpsScriptCredentialTestImports() + - cpsScriptCredentialTest("CONTROLLER BUILT-IN"); + cpsScriptCertCredentialTestScriptedPipeline("CONTROLLER BUILT-IN"); proj.setDefinition(new CpsFlowDefinition(script, false)); // Execute the build @@ -349,7 +349,7 @@ public void testCertKeyStoreReadableOnNodeLocal() throws Exception { WorkflowJob proj = r.jenkins.createProject(WorkflowJob.class, "proj"); String script = cpsScriptCredentialTestImports() + "node {\n" + - cpsScriptCredentialTest("CONTROLLER NODE") + + cpsScriptCertCredentialTestScriptedPipeline("CONTROLLER NODE") + "}\n"; proj.setDefinition(new CpsFlowDefinition(script, false)); @@ -377,7 +377,7 @@ public void testCertKeyStoreReadableOnNodeRemote() throws Exception { WorkflowJob proj = r.jenkins.createProject(WorkflowJob.class, "proj"); String script = cpsScriptCredentialTestImports() + "node(\"worker\") {\n" + - cpsScriptCredentialTest("REMOTE NODE") + + cpsScriptCertCredentialTestScriptedPipeline("REMOTE NODE") + "}\n"; proj.setDefinition(new CpsFlowDefinition(script, false)); @@ -395,7 +395,7 @@ public void testCertKeyStoreReadableOnNodeRemote() throws Exception { // Certificate credentials retrievability by withCredentials() step ///////////////////////////////////////////////////////////////// - String cpsScriptCredentialTestGetKeyValue() { + String cpsScriptCertCredentialTestGetKeyValue() { return "@NonCPS\n" + "def getKeyValue(def keystoreName, def keystoreFormat, def keyPassword, def alias) {\n" + " def p12file = new FileInputStream(keystoreName)\n" + @@ -408,11 +408,11 @@ String cpsScriptCredentialTestGetKeyValue() { "\n"; } - String cpsScriptCredentialTestWithCredentials(String runnerTag) { - return cpsScriptCredentialTestWithCredentials("myCert", "password", "1", runnerTag); + String cpsScriptCertCredentialTestWithCredentials(String runnerTag) { + return cpsScriptCertCredentialTestWithCredentials("myCert", "password", "1", runnerTag); } - String cpsScriptCredentialTestWithCredentials(String id, String password, String alias, String runnerTag) { + String cpsScriptCertCredentialTestWithCredentials(String id, String password, String alias, String runnerTag) { // Note: for some reason does not pass (env?.)myKeyAlias to closure return "def authentication='" + id + "';\n" + "def password='" + password + "';\n" + @@ -446,8 +446,8 @@ public void testCertWithCredentialsOnController() throws Exception { // Configure the build to use the credential WorkflowJob proj = r.jenkins.createProject(WorkflowJob.class, "proj"); String script = cpsScriptCredentialTestImports() + - cpsScriptCredentialTestGetKeyValue() + - cpsScriptCredentialTestWithCredentials("CONTROLLER BUILT-IN"); + cpsScriptCertCredentialTestGetKeyValue() + + cpsScriptCertCredentialTestWithCredentials("CONTROLLER BUILT-IN"); proj.setDefinition(new CpsFlowDefinition(script, false)); // Execute the build @@ -470,9 +470,9 @@ public void testCertWithCredentialsOnNodeLocal() throws Exception { // Configure the build to use the credential WorkflowJob proj = r.jenkins.createProject(WorkflowJob.class, "proj"); String script = cpsScriptCredentialTestImports() + - cpsScriptCredentialTestGetKeyValue() + + cpsScriptCertCredentialTestGetKeyValue() + "node {\n" + - cpsScriptCredentialTestWithCredentials("CONTROLLER NODE") + + cpsScriptCertCredentialTestWithCredentials("CONTROLLER NODE") + "}\n"; proj.setDefinition(new CpsFlowDefinition(script, false)); @@ -499,9 +499,9 @@ public void testCertWithCredentialsOnNodeRemote() throws Exception { // Configure the build to use the credential WorkflowJob proj = r.jenkins.createProject(WorkflowJob.class, "proj"); String script = cpsScriptCredentialTestImports() + - cpsScriptCredentialTestGetKeyValue() + + cpsScriptCertCredentialTestGetKeyValue() + "node(\"worker\") {\n" + - cpsScriptCredentialTestWithCredentials("REMOTE NODE") + + cpsScriptCertCredentialTestWithCredentials("REMOTE NODE") + "}\n"; proj.setDefinition(new CpsFlowDefinition(script, false)); From 9d3cb56f013bef7c8840eedec3ccf1bbe2c8ae4a Mon Sep 17 00:00:00 2001 From: Evgeny Klimov Date: Sun, 20 Nov 2022 15:49:05 +0100 Subject: [PATCH 11/21] CredentialsInPipelineTest: add testCertHttpRequest*() --- pom.xml | 7 ++ .../CredentialsInPipelineTest.java | 99 +++++++++++++++++++ 2 files changed, 106 insertions(+) diff --git a/pom.xml b/pom.xml index 0ea2a9c2c..c91e85623 100644 --- a/pom.xml +++ b/pom.xml @@ -179,6 +179,13 @@ 523.vd859a_4b_122e6 test + + org.jenkins-ci.plugins + http_request + + 1.16 + test + diff --git a/src/test/java/com/cloudbees/plugins/credentials/CredentialsInPipelineTest.java b/src/test/java/com/cloudbees/plugins/credentials/CredentialsInPipelineTest.java index 6444510a7..8df2e005b 100644 --- a/src/test/java/com/cloudbees/plugins/credentials/CredentialsInPipelineTest.java +++ b/src/test/java/com/cloudbees/plugins/credentials/CredentialsInPipelineTest.java @@ -515,4 +515,103 @@ public void testCertWithCredentialsOnNodeRemote() throws Exception { r.assertLogContains("END PRIVATE KEY", run); } + ///////////////////////////////////////////////////////////////// + // Certificate credentials retrievability by http-request-plugin + ///////////////////////////////////////////////////////////////// + + String cpsScriptCertCredentialTestHttpRequest(String runnerTag) { + return cpsScriptCredentialTestHttpRequest("myCert", runnerTag); + } + + 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 + return "def authentication='" + id + "';\n" + + "def 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('HTTP Request Plugin Status: '+ response.getStatus())\n" + + "println('HTTP Request Plugin Response: '+ response.getContent())\n" + + "\n"; + } + + @Test + @Issue("JENKINS-70101") + public void testCertHttpRequestOnController() throws Exception { + // Check that credentials are usable with pipeline script + // running without a node{} + prepareUploadedKeystore(); + + // Configure the build to use the credential + WorkflowJob proj = r.jenkins.createProject(WorkflowJob.class, "proj"); + String script = cpsScriptCertCredentialTestHttpRequest("CONTROLLER BUILT-IN"); + proj.setDefinition(new CpsFlowDefinition(script, false)); + + // Execute the build + WorkflowRun run = proj.scheduleBuild2(0).get(); + System.out.println(getLogAsStringPlaintext(run)); + + // Check expectations + r.assertBuildStatus(Result.SUCCESS, run); + // Got to the end? + r.assertLogContains("HTTP Request Plugin Response: ", run); + } + + @Test + @Issue("JENKINS-70101") + public void testCertHttpRequestOnNodeLocal() throws Exception { + // Check that credentials are usable with pipeline script + // running on a node{} (provided by the controller) + prepareUploadedKeystore(); + + // Configure the build to use the credential + WorkflowJob proj = r.jenkins.createProject(WorkflowJob.class, "proj"); + String script = + "node {\n" + + cpsScriptCertCredentialTestHttpRequest("CONTROLLER NODE") + + "}\n"; + proj.setDefinition(new CpsFlowDefinition(script, false)); + + // Execute the build + WorkflowRun run = proj.scheduleBuild2(0).get(); + System.out.println(getLogAsStringPlaintext(run)); + + // Check expectations + r.assertBuildStatus(Result.SUCCESS, run); + // Got to the end? + r.assertLogContains("HTTP Request Plugin Response: ", run); + } + + @Test + @Issue("JENKINS-70101") + public void testCertHttpRequestOnNodeRemote() throws Exception { + // Check that credentials are usable with pipeline script + // running on a remote node{} with separate JVM (check + // that remoting/snapshot work properly) + assumeThat("This test needs a separate build agent", this.setupAgent(), is(true)); + + prepareUploadedKeystore(); + + // Configure the build to use the credential + WorkflowJob proj = r.jenkins.createProject(WorkflowJob.class, "proj"); + String script = + "node(\"worker\") {\n" + + cpsScriptCertCredentialTestHttpRequest("REMOTE NODE") + + "}\n"; + proj.setDefinition(new CpsFlowDefinition(script, false)); + + // Execute the build + WorkflowRun run = proj.scheduleBuild2(0).get(); + System.out.println(getLogAsStringPlaintext(run)); + + // Check expectations + r.assertBuildStatus(Result.SUCCESS, run); + // Got to the end? + r.assertLogContains("HTTP Request Plugin Response: ", run); + } + } From 2b9fa6f5cfa57f93b4aae2f93fb56b3682f6129e Mon Sep 17 00:00:00 2001 From: Evgeny Klimov Date: Sun, 20 Nov 2022 16:40:58 +0100 Subject: [PATCH 12/21] CredentialsInPipelineTest: add testUsernamePasswordHttpRequest*() --- .../CredentialsInPipelineTest.java | 94 +++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/src/test/java/com/cloudbees/plugins/credentials/CredentialsInPipelineTest.java b/src/test/java/com/cloudbees/plugins/credentials/CredentialsInPipelineTest.java index 8df2e005b..2ec2cf142 100644 --- a/src/test/java/com/cloudbees/plugins/credentials/CredentialsInPipelineTest.java +++ b/src/test/java/com/cloudbees/plugins/credentials/CredentialsInPipelineTest.java @@ -37,6 +37,7 @@ import com.cloudbees.plugins.credentials.domains.Domain; import com.cloudbees.plugins.credentials.impl.CertificateCredentialsImpl; import com.cloudbees.plugins.credentials.impl.CertificateCredentialsImplTest; +import com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl; import com.gargoylesoftware.htmlunit.FormEncodingType; import com.gargoylesoftware.htmlunit.HttpMethod; import com.gargoylesoftware.htmlunit.Page; @@ -614,4 +615,97 @@ public void testCertHttpRequestOnNodeRemote() throws Exception { r.assertLogContains("HTTP Request Plugin Response: ", run); } + ///////////////////////////////////////////////////////////////// + // User/pass credentials tests + ///////////////////////////////////////////////////////////////// + + // Partially from UsernamePasswordCredentialsImplTest setup() + private void prepareUsernamePassword() throws IOException { + UsernamePasswordCredentialsImpl credentials = + new UsernamePasswordCredentialsImpl(null, + "abc123", "Bob’s laptop", + "bob", "s3cr3t"); + SystemCredentialsProvider.getInstance().getCredentials().add(credentials); + SystemCredentialsProvider.getInstance().save(); + } + + String cpsScriptUsernamePasswordCredentialTestHttpRequest(String runnerTag) { + return cpsScriptCredentialTestHttpRequest("abc123", runnerTag); + } + + @Test + @Issue("JENKINS-70101") + public void testUsernamePasswordHttpRequestOnController() throws Exception { + // Check that credentials are usable with pipeline script + // running without a node{} + prepareUsernamePassword(); + + // Configure the build to use the credential + WorkflowJob proj = r.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(); + System.out.println(getLogAsStringPlaintext(run)); + + // Check expectations + r.assertBuildStatus(Result.SUCCESS, run); + // Got to the end? + r.assertLogContains("HTTP Request Plugin Response: ", run); + } + + @Test + @Issue("JENKINS-70101") + public void testUsernamePasswordHttpRequestOnNodeLocal() throws Exception { + // Check that credentials are usable with pipeline script + // running on a node{} (provided by the controller) + prepareUsernamePassword(); + + // Configure the build to use the credential + WorkflowJob proj = r.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(); + System.out.println(getLogAsStringPlaintext(run)); + + // Check expectations + r.assertBuildStatus(Result.SUCCESS, run); + // Got to the end? + r.assertLogContains("HTTP Request Plugin Response: ", run); + } + + @Test + @Issue("JENKINS-70101") + public 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) + assumeThat("This test needs a separate build agent", this.setupAgent(), is(true)); + + prepareUsernamePassword(); + + // Configure the build to use the credential + WorkflowJob proj = r.jenkins.createProject(WorkflowJob.class, "proj"); + String script = + "node(\"worker\") {\n" + + cpsScriptUsernamePasswordCredentialTestHttpRequest("REMOTE NODE") + + "}\n"; + proj.setDefinition(new CpsFlowDefinition(script, false)); + + // Execute the build + WorkflowRun run = proj.scheduleBuild2(0).get(); + System.out.println(getLogAsStringPlaintext(run)); + + // Check expectations + r.assertBuildStatus(Result.SUCCESS, run); + // Got to the end? + r.assertLogContains("HTTP Request Plugin Response: ", run); + } + } From f2b859f83a585285d06e065fb33eee2de77fc1bb Mon Sep 17 00:00:00 2001 From: Evgeny Klimov Date: Sun, 20 Nov 2022 16:56:23 +0100 Subject: [PATCH 13/21] CredentialsInPipelineTest: comment about alias for withCredentials(certificate) --- .../plugins/credentials/CredentialsInPipelineTest.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/test/java/com/cloudbees/plugins/credentials/CredentialsInPipelineTest.java b/src/test/java/com/cloudbees/plugins/credentials/CredentialsInPipelineTest.java index 2ec2cf142..809cc4dad 100644 --- a/src/test/java/com/cloudbees/plugins/credentials/CredentialsInPipelineTest.java +++ b/src/test/java/com/cloudbees/plugins/credentials/CredentialsInPipelineTest.java @@ -414,7 +414,9 @@ String cpsScriptCertCredentialTestWithCredentials(String runnerTag) { } String cpsScriptCertCredentialTestWithCredentials(String id, String password, String alias, String runnerTag) { - // Note: for some reason does not pass (env?.)myKeyAlias to closure + // Note: does not pass a(ny) useful (env?.)myKeyAlias to closure + // https://issues.jenkins.io/browse/JENKINS-59331 + // https://github.com/jenkinsci/credentials-binding-plugin/blob/fcd22059ac48b87d0924ef17d5b351a3b7a89a97/src/main/java/org/jenkinsci/plugins/credentialsbinding/impl/CertificateMultiBinding.java#L80-L81 return "def authentication='" + id + "';\n" + "def password='" + password + "';\n" + "def alias='" + alias + "';\n" + From b3ae79cbb8ba7be8c596b69a4282139886d6bb41 Mon Sep 17 00:00:00 2001 From: Evgeny Klimov Date: Mon, 21 Nov 2022 09:39:12 +0100 Subject: [PATCH 14/21] CredentialsInPipelineTest: trace SecretBytes "directly" and via httpRequest() in the same pipeline (using same bytes) --- .../CredentialsInPipelineTest.java | 39 ++++++++++++++++--- 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/src/test/java/com/cloudbees/plugins/credentials/CredentialsInPipelineTest.java b/src/test/java/com/cloudbees/plugins/credentials/CredentialsInPipelineTest.java index 809cc4dad..eff13e087 100644 --- a/src/test/java/com/cloudbees/plugins/credentials/CredentialsInPipelineTest.java +++ b/src/test/java/com/cloudbees/plugins/credentials/CredentialsInPipelineTest.java @@ -523,13 +523,39 @@ public void testCertWithCredentialsOnNodeRemote() throws Exception { ///////////////////////////////////////////////////////////////// String cpsScriptCertCredentialTestHttpRequest(String runnerTag) { - return cpsScriptCredentialTestHttpRequest("myCert", runnerTag); + return cpsScriptCredentialTestHttpRequest("myCert", runnerTag, true); } - String cpsScriptCredentialTestHttpRequest(String id, String runnerTag) { + String cpsScriptCredentialTestHttpRequest(String id, String runnerTag, Boolean withLocalCertLookup) { // 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() return "def authentication='" + id + "';\n" + + "\n" + + "def msg\n" + + (withLocalCertLookup ? ( + "if (true) { // scoping\n" + + " msg = \"Finding credential...\"\n" + + " echo msg; 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; System.out.println(msg); System.err.println(msg);\n" + + " KeyStore keyStore = credential.getKeyStore();\n" + + " msg = \"Getting keystore source...\"\n" + + " echo msg; System.out.println(msg); System.err.println(msg);\n" + + " KeyStoreSource kss = ((CertificateCredentialsImpl) credential).getKeyStoreSource();\n" + + " msg = \"Getting keystore source bytes...\"\n" + + " echo msg; System.out.println(msg); System.err.println(msg);\n" + + " byte[] kssb = kss.getKeyStoreBytes();\n" + + "}\n" ) + : "" ) + + "\n" + + "msg = \"Querying HTTPS with cert...\"\n" + + "echo msg; 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" @@ -551,7 +577,8 @@ public void testCertHttpRequestOnController() throws Exception { // Configure the build to use the credential WorkflowJob proj = r.jenkins.createProject(WorkflowJob.class, "proj"); - String script = cpsScriptCertCredentialTestHttpRequest("CONTROLLER BUILT-IN"); + String script = cpsScriptCredentialTestImports() + + cpsScriptCertCredentialTestHttpRequest("CONTROLLER BUILT-IN"); proj.setDefinition(new CpsFlowDefinition(script, false)); // Execute the build @@ -573,7 +600,7 @@ public void testCertHttpRequestOnNodeLocal() throws Exception { // Configure the build to use the credential WorkflowJob proj = r.jenkins.createProject(WorkflowJob.class, "proj"); - String script = + String script = cpsScriptCredentialTestImports() + "node {\n" + cpsScriptCertCredentialTestHttpRequest("CONTROLLER NODE") + "}\n"; @@ -601,7 +628,7 @@ public void testCertHttpRequestOnNodeRemote() throws Exception { // Configure the build to use the credential WorkflowJob proj = r.jenkins.createProject(WorkflowJob.class, "proj"); - String script = + String script = cpsScriptCredentialTestImports() + "node(\"worker\") {\n" + cpsScriptCertCredentialTestHttpRequest("REMOTE NODE") + "}\n"; @@ -632,7 +659,7 @@ private void prepareUsernamePassword() throws IOException { } String cpsScriptUsernamePasswordCredentialTestHttpRequest(String runnerTag) { - return cpsScriptCredentialTestHttpRequest("abc123", runnerTag); + return cpsScriptCredentialTestHttpRequest("abc123", runnerTag, false); } @Test From 4a1d34e14a94994142f099185b1712e19661f248 Mon Sep 17 00:00:00 2001 From: Evgeny Klimov Date: Mon, 21 Nov 2022 11:31:32 +0100 Subject: [PATCH 15/21] CertificateCredentialsImpl: UploadedKeyStoreSource: update comment for getUploadedKeystore() --- .../plugins/credentials/impl/CertificateCredentialsImpl.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/cloudbees/plugins/credentials/impl/CertificateCredentialsImpl.java b/src/main/java/com/cloudbees/plugins/credentials/impl/CertificateCredentialsImpl.java index a92d84f48..306df4c4b 100644 --- a/src/main/java/com/cloudbees/plugins/credentials/impl/CertificateCredentialsImpl.java +++ b/src/main/java/com/cloudbees/plugins/credentials/impl/CertificateCredentialsImpl.java @@ -490,9 +490,9 @@ private Object readResolve() throws ObjectStreamException { } /** - * Returns the private key file name. + * Returns the private key + certificate file bytes. * - * @return the private key file name. + * @return the private key + certificate file bytes. */ public SecretBytes getUploadedKeystore() { return uploadedKeystoreBytes; From f6731fb824772c9ca8975f303fd8b2b03fdb25cf Mon Sep 17 00:00:00 2001 From: Evgeny Klimov Date: Mon, 21 Nov 2022 10:57:45 +0100 Subject: [PATCH 16/21] Revive CredentialsSnapshotTaker class --- pom.xml | 4 +- .../impl/CertificateCredentialsImpl.java | 17 ++++ .../CertificateCredentialsSnapshotTaker.java | 95 +++++++++++++++++++ 3 files changed, 115 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/cloudbees/plugins/credentials/impl/CertificateCredentialsSnapshotTaker.java diff --git a/pom.xml b/pom.xml index c91e85623..8d4beaf91 100644 --- a/pom.xml +++ b/pom.xml @@ -182,7 +182,9 @@ org.jenkins-ci.plugins http_request - + 1.16 test diff --git a/src/main/java/com/cloudbees/plugins/credentials/impl/CertificateCredentialsImpl.java b/src/main/java/com/cloudbees/plugins/credentials/impl/CertificateCredentialsImpl.java index 306df4c4b..50b5f3eac 100644 --- a/src/main/java/com/cloudbees/plugins/credentials/impl/CertificateCredentialsImpl.java +++ b/src/main/java/com/cloudbees/plugins/credentials/impl/CertificateCredentialsImpl.java @@ -23,6 +23,7 @@ */ package com.cloudbees.plugins.credentials.impl; +import com.cloudbees.plugins.credentials.CredentialsProvider; import com.cloudbees.plugins.credentials.CredentialsScope; import com.cloudbees.plugins.credentials.SecretBytes; import com.cloudbees.plugins.credentials.common.StandardCertificateCredentials; @@ -34,6 +35,7 @@ import hudson.model.AbstractDescribableImpl; import hudson.model.Descriptor; import hudson.model.Items; +import hudson.remoting.Channel; import hudson.util.FormValidation; import hudson.util.IOUtils; import hudson.util.Secret; @@ -138,6 +140,21 @@ private static char[] toCharArray(@NonNull Secret password) { return plainText == null ? null : plainText.toCharArray(); } + /** + * When serializing over a {@link Channel} ensure that we send a self-contained version. + * + * @return the object instance to write to the stream. + */ + private Object writeReplace() { + if (/* XStream */ Channel.current() == null + || /* already safe to serialize */ keyStoreSource + .isSnapshotSource() + ) { + return this; + } + return CredentialsProvider.snapshot(this); + } + /** * Returns the {@link KeyStore} containing the certificate. * diff --git a/src/main/java/com/cloudbees/plugins/credentials/impl/CertificateCredentialsSnapshotTaker.java b/src/main/java/com/cloudbees/plugins/credentials/impl/CertificateCredentialsSnapshotTaker.java new file mode 100644 index 000000000..dedc8dc44 --- /dev/null +++ b/src/main/java/com/cloudbees/plugins/credentials/impl/CertificateCredentialsSnapshotTaker.java @@ -0,0 +1,95 @@ +/* + * The MIT License + * + * Copyright (c) 2011-2016, CloudBees, Inc., Stephen Connolly. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + + +package com.cloudbees.plugins.credentials.impl; + +import com.cloudbees.plugins.credentials.CredentialsSnapshotTaker; +import com.cloudbees.plugins.credentials.SecretBytes; +import com.cloudbees.plugins.credentials.common.StandardCertificateCredentials; +import hudson.Extension; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; +import java.util.Arrays; + +/** + * The {@link CredentialsSnapshotTaker} for {@link StandardCertificateCredentials}. + * Taking a snapshot of the credential ensures that all the details are captured + * within the credential. + * + * @since 1.14 + * + * Historic note: This code was dropped from {@link CertificateCredentialsImpl} + * codebase along with most of FileOnMasterKeyStoreSource (deprecated and headed + * towards eventual deletion) due to SECURITY-1322, see more details at + * https://www.jenkins.io/security/advisory/2019-05-21/ + * In hind-sight, this snapshot taker was needed to let the + * {@link CertificateCredentialsImpl.UploadedKeyStoreSource} be used + * on remote agents. + */ +@Extension +public class CertificateCredentialsSnapshotTaker extends CredentialsSnapshotTaker { + + /** + * {@inheritDoc} + */ + @Override + public Class type() { + return StandardCertificateCredentials.class; + } + + /** + * {@inheritDoc} + */ + @Override + public StandardCertificateCredentials snapshot(StandardCertificateCredentials credentials) { + if (credentials instanceof CertificateCredentialsImpl) { + final CertificateCredentialsImpl.KeyStoreSource keyStoreSource = ((CertificateCredentialsImpl) credentials).getKeyStoreSource(); + if (keyStoreSource.isSnapshotSource()) { + return credentials; + } + return new CertificateCredentialsImpl(credentials.getScope(), credentials.getId(), + credentials.getDescription(), credentials.getPassword().getEncryptedValue(), + new CertificateCredentialsImpl.UploadedKeyStoreSource(SecretBytes.fromBytes(keyStoreSource.getKeyStoreBytes()))); + } + + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + final char[] password = credentials.getPassword().getPlainText().toCharArray(); + try { + credentials.getKeyStore().store(bos, password); + bos.close(); + } catch (KeyStoreException | IOException | NoSuchAlgorithmException | CertificateException e) { + return credentials; // as-is + } finally { + Arrays.fill(password, (char) 0); + } + + return new CertificateCredentialsImpl(credentials.getScope(), credentials.getId(), + credentials.getDescription(), credentials.getPassword().getEncryptedValue(), + new CertificateCredentialsImpl.UploadedKeyStoreSource(SecretBytes.fromBytes(bos.toByteArray()))); + } +} From 9d32b42c28f5612dda6b747372f5eb4542eac29e Mon Sep 17 00:00:00 2001 From: Evgeny Klimov Date: Mon, 21 Nov 2022 11:39:51 +0100 Subject: [PATCH 17/21] CertificateCredentialsImpl: UploadedKeyStoreSource: let "Secret uploadedKeystore" be used to serialize --- .../impl/CertificateCredentialsImpl.java | 36 +++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/cloudbees/plugins/credentials/impl/CertificateCredentialsImpl.java b/src/main/java/com/cloudbees/plugins/credentials/impl/CertificateCredentialsImpl.java index 50b5f3eac..603579e2a 100644 --- a/src/main/java/com/cloudbees/plugins/credentials/impl/CertificateCredentialsImpl.java +++ b/src/main/java/com/cloudbees/plugins/credentials/impl/CertificateCredentialsImpl.java @@ -436,17 +436,18 @@ public static class UploadedKeyStoreSource extends KeyStoreSource implements Ser /** * The old uploaded keystore. + * Still used for snapshot taking, with contents independent of Jenkins instance and JVM. */ @CheckForNull @Deprecated - private transient Secret uploadedKeystore; + private Secret uploadedKeystore; /** * The uploaded keystore. * * @since 2.1.5 */ @CheckForNull - private final SecretBytes uploadedKeystoreBytes; + private SecretBytes uploadedKeystoreBytes; /** * Our constructor. @@ -474,6 +475,19 @@ public UploadedKeyStoreSource(@CheckForNull SecretBytes uploadedKeystore) { this.uploadedKeystoreBytes = uploadedKeystore; } + /** + * Our constructor for serialization (e.g. to remote agents, whose SecretBytes + * in another JVM use a different static KEY); would re-encode. + * + * @param uploadedKeystore the keystore content. + * @deprecated + */ + @SuppressWarnings("unused") // by stapler + @Deprecated + public UploadedKeyStoreSource(@CheckForNull Secret uploadedKeystore) { + this.uploadedKeystore = uploadedKeystore; + } + /** * Constructor able to receive file directly * @@ -492,6 +506,18 @@ public UploadedKeyStoreSource(FileItem uploadedCertFile, @CheckForNull SecretByt this.uploadedKeystoreBytes = uploadedKeystore; } + /** + * Request that if the less-efficient but more-portable Secret + * is involved (e.g. to cross the remoting gap to another JVM), + * we use the more secure and efficient SecretBytes. + */ + public void useSecretBytes() { + if (this.uploadedKeystore != null && this.uploadedKeystoreBytes == null) { + this.uploadedKeystoreBytes = SecretBytes.fromBytes(DescriptorImpl.toByteArray(this.uploadedKeystore)); + this.uploadedKeystore = null; + } + } + /** * Migrate to the new field. * @@ -512,6 +538,9 @@ private Object readResolve() throws ObjectStreamException { * @return the private key + certificate file bytes. */ public SecretBytes getUploadedKeystore() { + if (uploadedKeystore != null && uploadedKeystoreBytes == null) { + return SecretBytes.fromBytes(DescriptorImpl.toByteArray(uploadedKeystore)); + } return uploadedKeystoreBytes; } @@ -521,6 +550,9 @@ public SecretBytes getUploadedKeystore() { @NonNull @Override public byte[] getKeyStoreBytes() { + if (uploadedKeystore != null && uploadedKeystoreBytes == null) { + return DescriptorImpl.toByteArray(uploadedKeystore); + } return SecretBytes.getPlainData(uploadedKeystoreBytes); } From 3694a909b9c32d4515dce7f482d067a1e030a7ed Mon Sep 17 00:00:00 2001 From: Evgeny Klimov Date: Mon, 21 Nov 2022 13:57:48 +0100 Subject: [PATCH 18/21] CertificateCredentialsSnapshotTaker: for snapshot to be self-contained, must use Secret not SecretBytes --- .../impl/CertificateCredentialsSnapshotTaker.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/cloudbees/plugins/credentials/impl/CertificateCredentialsSnapshotTaker.java b/src/main/java/com/cloudbees/plugins/credentials/impl/CertificateCredentialsSnapshotTaker.java index dedc8dc44..90697d7e5 100644 --- a/src/main/java/com/cloudbees/plugins/credentials/impl/CertificateCredentialsSnapshotTaker.java +++ b/src/main/java/com/cloudbees/plugins/credentials/impl/CertificateCredentialsSnapshotTaker.java @@ -29,6 +29,8 @@ import com.cloudbees.plugins.credentials.SecretBytes; import com.cloudbees.plugins.credentials.common.StandardCertificateCredentials; import hudson.Extension; +import hudson.util.Secret; + import java.io.ByteArrayOutputStream; import java.io.IOException; import java.security.KeyStoreException; @@ -74,7 +76,7 @@ public StandardCertificateCredentials snapshot(StandardCertificateCredentials cr } return new CertificateCredentialsImpl(credentials.getScope(), credentials.getId(), credentials.getDescription(), credentials.getPassword().getEncryptedValue(), - new CertificateCredentialsImpl.UploadedKeyStoreSource(SecretBytes.fromBytes(keyStoreSource.getKeyStoreBytes()))); + new CertificateCredentialsImpl.UploadedKeyStoreSource(CertificateCredentialsImpl.UploadedKeyStoreSource.DescriptorImpl.toSecret(keyStoreSource.getKeyStoreBytes()))); } ByteArrayOutputStream bos = new ByteArrayOutputStream(); @@ -90,6 +92,6 @@ public StandardCertificateCredentials snapshot(StandardCertificateCredentials cr return new CertificateCredentialsImpl(credentials.getScope(), credentials.getId(), credentials.getDescription(), credentials.getPassword().getEncryptedValue(), - new CertificateCredentialsImpl.UploadedKeyStoreSource(SecretBytes.fromBytes(bos.toByteArray()))); + new CertificateCredentialsImpl.UploadedKeyStoreSource(CertificateCredentialsImpl.UploadedKeyStoreSource.DescriptorImpl.toSecret(bos.toByteArray()))); } } From 90e5a218cb4b66af1a3a61294d34fb7b9cc3d8a6 Mon Sep 17 00:00:00 2001 From: Evgeny Klimov Date: Mon, 21 Nov 2022 13:47:40 +0100 Subject: [PATCH 19/21] CertificateCredentialsImpl: UploadedKeyStoreSource: make isSnapshotSource() depend on Channel.current() == null --- .../credentials/impl/CertificateCredentialsImpl.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/cloudbees/plugins/credentials/impl/CertificateCredentialsImpl.java b/src/main/java/com/cloudbees/plugins/credentials/impl/CertificateCredentialsImpl.java index 603579e2a..ad9b9dbd6 100644 --- a/src/main/java/com/cloudbees/plugins/credentials/impl/CertificateCredentialsImpl.java +++ b/src/main/java/com/cloudbees/plugins/credentials/impl/CertificateCredentialsImpl.java @@ -569,7 +569,11 @@ public long getKeyStoreLastModified() { */ @Override public boolean isSnapshotSource() { - return true; + //return this.snapshotSecretBytes; + // If context is local, clone SecretBytes directly (only + // usable in same JVM). Otherwise use Secret for transport + // (see {@link CertificateCredentialsSnapshotTaker}. + return (/* XStream */ Channel.current() == null); } /** From 8e6831f4d1d2121ebde603eae706b9dbf596ae74 Mon Sep 17 00:00:00 2001 From: Evgeny Klimov Date: Mon, 21 Nov 2022 15:18:09 +0100 Subject: [PATCH 20/21] CredentialsInPipelineTest: cleanup imports --- .../CredentialsInPipelineTest.java | 45 +------------------ 1 file changed, 1 insertion(+), 44 deletions(-) diff --git a/src/test/java/com/cloudbees/plugins/credentials/CredentialsInPipelineTest.java b/src/test/java/com/cloudbees/plugins/credentials/CredentialsInPipelineTest.java index eff13e087..937335b5e 100644 --- a/src/test/java/com/cloudbees/plugins/credentials/CredentialsInPipelineTest.java +++ b/src/test/java/com/cloudbees/plugins/credentials/CredentialsInPipelineTest.java @@ -24,49 +24,17 @@ package com.cloudbees.plugins.credentials; -import com.cloudbees.hudson.plugins.folder.Folder; -import com.cloudbees.hudson.plugins.folder.properties.FolderCredentialsProvider; -import com.cloudbees.plugins.credentials.Credentials; -import com.cloudbees.plugins.credentials.CredentialsNameProvider; -import com.cloudbees.plugins.credentials.CredentialsProvider; -import com.cloudbees.plugins.credentials.CredentialsStore; -import com.cloudbees.plugins.credentials.SecretBytes; -import com.cloudbees.plugins.credentials.SystemCredentialsProvider; -import com.cloudbees.plugins.credentials.common.CertificateCredentials; -import com.cloudbees.plugins.credentials.common.StandardCertificateCredentials; -import com.cloudbees.plugins.credentials.domains.Domain; import com.cloudbees.plugins.credentials.impl.CertificateCredentialsImpl; import com.cloudbees.plugins.credentials.impl.CertificateCredentialsImplTest; import com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl; -import com.gargoylesoftware.htmlunit.FormEncodingType; -import com.gargoylesoftware.htmlunit.HttpMethod; -import com.gargoylesoftware.htmlunit.Page; -import com.gargoylesoftware.htmlunit.WebRequest; -import com.gargoylesoftware.htmlunit.html.DomNode; -import com.gargoylesoftware.htmlunit.html.DomNodeList; -import com.gargoylesoftware.htmlunit.html.HtmlElementUtil; -import com.gargoylesoftware.htmlunit.html.HtmlFileInput; -import com.gargoylesoftware.htmlunit.html.HtmlForm; -import com.gargoylesoftware.htmlunit.html.HtmlOption; -import com.gargoylesoftware.htmlunit.html.HtmlPage; -import com.gargoylesoftware.htmlunit.html.HtmlRadioButtonInput; -import hudson.FilePath; -import hudson.Util; -import hudson.cli.CLICommandInvoker; -import hudson.cli.UpdateJobCommand; import hudson.model.Descriptor; -import hudson.model.ItemGroup; -import hudson.model.Job; import hudson.model.Node; import hudson.model.Result; import hudson.model.Slave; -import hudson.security.ACL; import hudson.slaves.CommandLauncher; import hudson.slaves.ComputerLauncher; import hudson.slaves.DumbSlave; import hudson.slaves.RetentionStrategy; -import hudson.util.Secret; -import jenkins.model.Jenkins; import org.apache.commons.io.FileUtils; import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition; import org.jenkinsci.plugins.workflow.job.WorkflowJob; @@ -79,25 +47,14 @@ import org.junit.rules.TemporaryFolder; import org.jvnet.hudson.test.Issue; import org.jvnet.hudson.test.JenkinsRule; -import org.jvnet.hudson.test.recipes.LocalData; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.net.URL; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; import java.nio.file.Files; -import java.util.Base64; -import java.util.Collections; -import java.util.List; - -import static hudson.cli.CLICommandInvoker.Matcher.failedWith; -import static hudson.cli.CLICommandInvoker.Matcher.succeeded; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.hasSize; -import static org.junit.Assert.*; + import static org.hamcrest.CoreMatchers.*; import static org.junit.Assume.assumeThat; From 3213f694ca1c106154305212a8e428231bf989dc Mon Sep 17 00:00:00 2001 From: Evgeny Klimov Date: Mon, 21 Nov 2022 15:19:30 +0100 Subject: [PATCH 21/21] CredentialsInPipelineTest: cleanup verbosePipelines --- .../CredentialsInPipelineTest.java | 39 +++++++++++-------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/src/test/java/com/cloudbees/plugins/credentials/CredentialsInPipelineTest.java b/src/test/java/com/cloudbees/plugins/credentials/CredentialsInPipelineTest.java index 937335b5e..579318a7a 100644 --- a/src/test/java/com/cloudbees/plugins/credentials/CredentialsInPipelineTest.java +++ b/src/test/java/com/cloudbees/plugins/credentials/CredentialsInPipelineTest.java @@ -68,6 +68,11 @@ public class CredentialsInPipelineTest { * Initially tied to JENKINS-70101 research. */ + // 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="CredentialsInPipelineTest" + private boolean verbosePipelines = false; + @Rule public JenkinsRule r = new JenkinsRule(); @@ -288,7 +293,7 @@ public void testCertKeyStoreReadableOnController() throws Exception { // Execute the build WorkflowRun run = proj.scheduleBuild2(0).get(); - System.out.println(getLogAsStringPlaintext(run)); + if (verbosePipelines) System.out.println(getLogAsStringPlaintext(run)); // Check expectations r.assertBuildStatus(Result.SUCCESS, run); @@ -313,7 +318,7 @@ public void testCertKeyStoreReadableOnNodeLocal() throws Exception { // Execute the build WorkflowRun run = proj.scheduleBuild2(0).get(); - System.out.println(getLogAsStringPlaintext(run)); + if (verbosePipelines) System.out.println(getLogAsStringPlaintext(run)); // Check expectations r.assertBuildStatus(Result.SUCCESS, run); @@ -341,7 +346,7 @@ public void testCertKeyStoreReadableOnNodeRemote() throws Exception { // Execute the build WorkflowRun run = proj.scheduleBuild2(0).get(); - System.out.println(getLogAsStringPlaintext(run)); + if (verbosePipelines) System.out.println(getLogAsStringPlaintext(run)); // Check expectations r.assertBuildStatus(Result.SUCCESS, run); @@ -412,7 +417,7 @@ public void testCertWithCredentialsOnController() throws Exception { // Execute the build WorkflowRun run = proj.scheduleBuild2(0).get(); - System.out.println(getLogAsStringPlaintext(run)); + if (verbosePipelines) System.out.println(getLogAsStringPlaintext(run)); // Check expectations r.assertBuildStatus(Result.SUCCESS, run); @@ -438,7 +443,7 @@ public void testCertWithCredentialsOnNodeLocal() throws Exception { // Execute the build WorkflowRun run = proj.scheduleBuild2(0).get(); - System.out.println(getLogAsStringPlaintext(run)); + if (verbosePipelines) System.out.println(getLogAsStringPlaintext(run)); // Check expectations r.assertBuildStatus(Result.SUCCESS, run); @@ -467,7 +472,7 @@ public void testCertWithCredentialsOnNodeRemote() throws Exception { // Execute the build WorkflowRun run = proj.scheduleBuild2(0).get(); - System.out.println(getLogAsStringPlaintext(run)); + if (verbosePipelines) System.out.println(getLogAsStringPlaintext(run)); // Check expectations r.assertBuildStatus(Result.SUCCESS, run); @@ -493,26 +498,26 @@ String cpsScriptCredentialTestHttpRequest(String id, String runnerTag, Boolean w + (withLocalCertLookup ? ( "if (true) { // scoping\n" + " msg = \"Finding credential...\"\n" - + " echo msg; System.out.println(msg); System.err.println(msg);\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; System.out.println(msg); System.err.println(msg);\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; System.out.println(msg); System.err.println(msg);\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; System.out.println(msg); System.err.println(msg);\n" + + " echo msg;" + (verbosePipelines ? " System.out.println(msg); System.err.println(msg)" : "" ) + ";\n" + " byte[] kssb = kss.getKeyStoreBytes();\n" + "}\n" ) : "" ) + "\n" + "msg = \"Querying HTTPS with cert...\"\n" - + "echo msg; System.out.println(msg); System.err.println(msg);\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" @@ -540,7 +545,7 @@ public void testCertHttpRequestOnController() throws Exception { // Execute the build WorkflowRun run = proj.scheduleBuild2(0).get(); - System.out.println(getLogAsStringPlaintext(run)); + if (verbosePipelines) System.out.println(getLogAsStringPlaintext(run)); // Check expectations r.assertBuildStatus(Result.SUCCESS, run); @@ -565,7 +570,7 @@ public void testCertHttpRequestOnNodeLocal() throws Exception { // Execute the build WorkflowRun run = proj.scheduleBuild2(0).get(); - System.out.println(getLogAsStringPlaintext(run)); + if (verbosePipelines) System.out.println(getLogAsStringPlaintext(run)); // Check expectations r.assertBuildStatus(Result.SUCCESS, run); @@ -593,7 +598,7 @@ public void testCertHttpRequestOnNodeRemote() throws Exception { // Execute the build WorkflowRun run = proj.scheduleBuild2(0).get(); - System.out.println(getLogAsStringPlaintext(run)); + if (verbosePipelines) System.out.println(getLogAsStringPlaintext(run)); // Check expectations r.assertBuildStatus(Result.SUCCESS, run); @@ -633,7 +638,7 @@ public void testUsernamePasswordHttpRequestOnController() throws Exception { // Execute the build WorkflowRun run = proj.scheduleBuild2(0).get(); - System.out.println(getLogAsStringPlaintext(run)); + if (verbosePipelines) System.out.println(getLogAsStringPlaintext(run)); // Check expectations r.assertBuildStatus(Result.SUCCESS, run); @@ -658,7 +663,7 @@ public void testUsernamePasswordHttpRequestOnNodeLocal() throws Exception { // Execute the build WorkflowRun run = proj.scheduleBuild2(0).get(); - System.out.println(getLogAsStringPlaintext(run)); + if (verbosePipelines) System.out.println(getLogAsStringPlaintext(run)); // Check expectations r.assertBuildStatus(Result.SUCCESS, run); @@ -686,7 +691,7 @@ public void testUsernamePasswordHttpRequestOnNodeRemote() throws Exception { // Execute the build WorkflowRun run = proj.scheduleBuild2(0).get(); - System.out.println(getLogAsStringPlaintext(run)); + if (verbosePipelines) System.out.println(getLogAsStringPlaintext(run)); // Check expectations r.assertBuildStatus(Result.SUCCESS, run);