Skip to content

Commit 8bab8d8

Browse files
committed
HttpRequestStepCredentialsTest: import tests and helper code to (optionally) verify that credentials pass over to remote agents correctly [JENKINS-70101]
Signed-off-by: Jim Klimov <jimklimov+jenkinsci@gmail.com>
1 parent fd88890 commit 8bab8d8

File tree

1 file changed

+263
-3
lines changed

1 file changed

+263
-3
lines changed

src/test/java/jenkins/plugins/http_request/HttpRequestStepCredentialsTest.java

Lines changed: 263 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import static org.hamcrest.MatcherAssert.assertThat;
44
import static org.hamcrest.Matchers.*;
5+
import static org.junit.jupiter.api.Assumptions.assumeTrue;
56

67
import com.cloudbees.plugins.credentials.CredentialsProvider;
78
import com.cloudbees.plugins.credentials.CredentialsScope;
@@ -12,15 +13,21 @@
1213
import com.cloudbees.plugins.credentials.domains.Domain;
1314
import com.cloudbees.plugins.credentials.impl.CertificateCredentialsImpl;
1415
import com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl;
16+
17+
import hudson.model.Descriptor;
1518
import hudson.model.Descriptor.FormException;
1619
import hudson.model.Fingerprint;
20+
import hudson.model.Label;
21+
import hudson.model.Node;
1722
import hudson.model.Result;
1823

1924
import java.io.ByteArrayOutputStream;
2025
import java.io.File;
2126
import java.io.IOException;
2227
import java.nio.file.Files;
2328
import java.util.Collections;
29+
30+
import hudson.model.Slave;
2431
import jenkins.model.Jenkins;
2532

2633
import org.apache.commons.io.FileUtils;
@@ -66,6 +73,15 @@ class HttpRequestStepCredentialsTest extends HttpRequestTestBase {
6673
*/
6774
private final boolean verbosePipelines = false;
6875

76+
// Data for build agent setup
77+
/** Build agent label expected by test cases for remote logic execution
78+
* and data transfer [JENKINS-70101] */
79+
private final static String agentLabelString = "cred-test-worker";
80+
// Can this be reused for many test cases?
81+
private Slave agent = null;
82+
/** Tri-state Unknown/started/not usable [JENKINS-70101] */
83+
private Boolean agentUsable = null;
84+
6985
// From CertificateCredentialImplTest in credentials-plugin
7086
/** Temporary location for keystore files.
7187
* @see #p12simple
@@ -93,6 +109,34 @@ class HttpRequestStepCredentialsTest extends HttpRequestTestBase {
93109
*/
94110
private CredentialsStore store = null;
95111

112+
/** True if we can use remote agent tests, and the credentials plugin version
113+
* here is expected to transfer secret data across the Channel correctly
114+
* (assuming issue JENKINS-70101 is fixed in that plugin).
115+
*/
116+
static private Boolean credentialsPluginDoesSnapshotsRight = null;
117+
static {
118+
try {
119+
Class.forName(
120+
"com.cloudbees.plugins.credentials.impl.CertificateCredentialsSnapshotTaker",
121+
false, HttpRequestStepCredentialsTest.class.getClassLoader());
122+
credentialsPluginDoesSnapshotsRight = true;
123+
} catch (ClassNotFoundException ignored) {
124+
credentialsPluginDoesSnapshotsRight = false;
125+
} catch (ExceptionInInitializerError ignored) {
126+
// Per https://www.baeldung.com/java-check-class-exists the Class.forName()
127+
// calls a static initializer which may fail (at least for the default
128+
// single-argument version of the method), but still -- if we get that far,
129+
// the class exists so we are probably running the version of plugin with
130+
// https://github.com/jenkinsci/credentials-plugin/pull/391
131+
credentialsPluginDoesSnapshotsRight = true;
132+
}
133+
}
134+
/** Honour the check via {@link #credentialsPluginDoesSnapshotsRight} [false],
135+
* or try (and possibly fail) with any implementation/version of the
136+
* credentials plugin [true]?
137+
*/
138+
private Boolean credentialsPluginTestRemoteAlways = false;
139+
96140
private static StandardCredentials getInvalidCredential() throws FormException {
97141
String username = "bad-user";
98142
String password = "bad-password";
@@ -166,6 +210,9 @@ void enableSystemCredentialsProvider() {
166210
}
167211
}
168212
assertThat("The system credentials provider is enabled", store, notNullValue());
213+
214+
// Primarily needed for remote-agent tests per JENKINS-70101?
215+
j.jenkins.setCrumbIssuer(null);
169216
}
170217

171218
/////////////////////////////////////////////////////////////////
@@ -352,9 +399,10 @@ private String cpsScriptCertCredentialTestHttpRequest(String runnerTag) {
352399
/////////////////////////////////////////////////////////////////
353400

354401
// A set of tests with certificate credentials in different contexts
355-
// TODO: Test on remote agent as in https://github.com/jenkinsci/credentials-plugin/pull/391
356-
// but this requires that PR to be merged first, so credentials-plugin
357-
// processes snapshot() and readable keystore data gets to remote agent.
402+
// NOTE: Test cases on remote agent require the PR
403+
// https://github.com/jenkinsci/credentials-plugin/pull/391
404+
// to be merged first, so credentials-plugin processes snapshot()
405+
// and readable keystore data gets to remote agent.
358406
// Note that the tests below focus on ability of the plugin to load and
359407
// process the key store specified by the credential, rather than that
360408
// it is usable further. It would be a separate effort to mock up a web
@@ -491,6 +539,86 @@ void testCertTrustedHttpRequestOnNodeLocal() throws Exception {
491539
j.assertLogContains("Treating UnknownHostException", run);
492540
}
493541

542+
/////////////////////////////////////////////////////////////////
543+
// Helpers for pipeline tests with remote agents
544+
/////////////////////////////////////////////////////////////////
545+
546+
private Boolean isAvailableAgent() {
547+
// Can be used to skip optional tests if we know we could not set up an agent
548+
if (agent == null)
549+
return false;
550+
return agentUsable;
551+
}
552+
553+
private Boolean setupAgent() throws OutOfMemoryError, Exception {
554+
if (isAvailableAgent())
555+
return true;
556+
557+
// See how credentialsPluginTestRemoteAlways is determined above
558+
// and revise if the ultimately merged fix that started as
559+
// https://github.com/jenkinsci/credentials-plugin/pull/391
560+
// gets changed before the merge or later on...
561+
String msg_70101 = "This test needs a version of credentials-plugin with a fix for JENKINS-70101, and that does not seem to be deployed here";
562+
if (!credentialsPluginTestRemoteAlways)
563+
assumeTrue(credentialsPluginDoesSnapshotsRight, msg_70101);
564+
565+
// else: credentialsPluginTestRemoteAlways, even if we fail
566+
if (!credentialsPluginDoesSnapshotsRight) {
567+
System.err.println("WARNING: " + msg_70101 + "; this test run was configured to try remote agents anyway");
568+
// return false;
569+
}
570+
571+
// Note we anticipate this might fail e.g. due to system resources;
572+
// it should not block the whole test suite from running
573+
// (we would just dynamically skip certain test cases)
574+
try {
575+
// Define a "Permanent Agent"
576+
Label agentLabel = Label.get(agentLabelString);
577+
agent = j.createOnlineSlave(agentLabel);
578+
agent.setNodeDescription("Worker in another JVM, remoting used");
579+
agent.setNumExecutors(1);
580+
agent.setMode(Node.Mode.EXCLUSIVE);
581+
///agent.setRetentionStrategy(new RetentionStrategy.Always());
582+
583+
/*
584+
// Add node envvars
585+
List<Entry> env = new ArrayList<Entry>();
586+
env.add(new Entry("key1","value1"));
587+
env.add(new Entry("key2","value2"));
588+
EnvironmentVariablesNodeProperty envPro = new EnvironmentVariablesNodeProperty(env);
589+
agent.getNodeProperties().add(envPro);
590+
*/
591+
592+
String agentLog = null;
593+
agentUsable = false;
594+
for (long i = 0; i < 5; i++) {
595+
Thread.sleep(1000);
596+
agentLog = agent.getComputer().getLog();
597+
if (i == 2 && (agentLog == null || agentLog.isEmpty())) {
598+
// Give it a little time to autostart, then kick it up if needed:
599+
agent.getComputer().connect(true); // "always" should have started it; avoid duplicate runs
600+
}
601+
if (agentLog != null && agentLog.contains("Agent successfully connected and online")) {
602+
agentUsable = true;
603+
break;
604+
}
605+
}
606+
System.out.println("Spawned build agent " +
607+
"usability: " + agentUsable.toString() +
608+
"; connection log:" + (agentLog == null ? " <null>" : "\n" + agentLog));
609+
} catch (Descriptor.FormException | NullPointerException e) {
610+
agentUsable = false;
611+
}
612+
613+
return agentUsable;
614+
}
615+
616+
/////////////////////////////////////////////////////////////////
617+
// Certificate credentials retrievability by http-request-plugin
618+
// in a set of local+remote JVMs (should work with versions of
619+
// credentials plugin where issue JENKINS-70101 is fixed)
620+
/////////////////////////////////////////////////////////////////
621+
494622
/** Simplified version of simple/trusted tests with "myCert" credential id,
495623
* transplanted from https://github.com/jenkinsci/credentials-plugin/pull/391 :
496624
* Check that Certificate credentials are usable with pipeline script
@@ -548,4 +676,136 @@ void testCertHttpRequestOnNodeLocal() throws Exception {
548676
j.assertLogContains("HTTP Request Plugin Response: ", run);
549677
j.assertLogContains("Using authentication: myCert", run);
550678
}
679+
680+
/**
681+
* Check that Certificate credentials are usable with pipeline script
682+
* running on a remote {@code node{}} with separate JVM (e.g.
683+
* check that remoting and credential snapshot work properly).
684+
*/
685+
@Test
686+
@Issue("JENKINS-70101")
687+
void testCertHttpRequestOnNodeRemote() throws Exception {
688+
assumeTrue(this.setupAgent() == true, "This test needs a separate build agent");
689+
690+
prepareUploadedKeystore();
691+
692+
// Configure the build to use the credential
693+
WorkflowJob proj = j.jenkins.createProject(WorkflowJob.class, "proj");
694+
String script =
695+
cpsScriptCredentialTestImports() +
696+
"node(\"" + agentLabelString + "\") {\n" +
697+
cpsScriptCertCredentialTestHttpRequest("REMOTE NODE") +
698+
"}\n";
699+
proj.setDefinition(new CpsFlowDefinition(script, false));
700+
701+
// Execute the build
702+
WorkflowRun run = proj.scheduleBuild2(0).get();
703+
if (verbosePipelines) System.out.println(getLogAsStringPlaintext(run));
704+
705+
// Check expectations
706+
j.assertBuildStatus(Result.SUCCESS, run);
707+
// Got to the end?
708+
j.assertLogContains("HTTP Request Plugin Response: ", run);
709+
}
710+
711+
/////////////////////////////////////////////////////////////////
712+
// User/pass credentials tests
713+
/////////////////////////////////////////////////////////////////
714+
715+
// Partially from UsernamePasswordCredentialsImplTest setup()
716+
private void prepareUsernamePassword() throws IOException, FormException {
717+
UsernamePasswordCredentialsImpl credentials =
718+
new UsernamePasswordCredentialsImpl(null,
719+
"abc123", "Bob’s laptop",
720+
"bob", "s3cr3t");
721+
SystemCredentialsProvider.getInstance().getCredentials().add(credentials);
722+
SystemCredentialsProvider.getInstance().save();
723+
}
724+
725+
private String cpsScriptUsernamePasswordCredentialTestHttpRequest(String runnerTag) {
726+
return cpsScriptCredentialTestHttpRequest("abc123", runnerTag, false);
727+
}
728+
729+
/** Check that Username credentials are usable with pipeline script
730+
* running without a {@code node{}} block.
731+
*/
732+
@Test
733+
@Issue("JENKINS-70101")
734+
void testUsernamePasswordHttpRequestOnController() throws Exception {
735+
prepareUsernamePassword();
736+
737+
// Configure the build to use the credential
738+
WorkflowJob proj = j.jenkins.createProject(WorkflowJob.class, "proj");
739+
String script =
740+
cpsScriptUsernamePasswordCredentialTestHttpRequest("CONTROLLER BUILT-IN");
741+
proj.setDefinition(new CpsFlowDefinition(script, false));
742+
743+
// Execute the build
744+
WorkflowRun run = proj.scheduleBuild2(0).get();
745+
if (verbosePipelines) System.out.println(getLogAsStringPlaintext(run));
746+
747+
// Check expectations
748+
j.assertBuildStatus(Result.SUCCESS, run);
749+
// Got to the end?
750+
j.assertLogContains("HTTP Request Plugin Response: ", run);
751+
}
752+
753+
/** Check that Username credentials are usable with pipeline script
754+
* running on a {@code node{}} (provided by the controller JVM).
755+
*/
756+
@Test
757+
@Issue("JENKINS-70101")
758+
void testUsernamePasswordHttpRequestOnNodeLocal() throws Exception {
759+
prepareUsernamePassword();
760+
761+
// Configure the build to use the credential
762+
WorkflowJob proj = j.jenkins.createProject(WorkflowJob.class, "proj");
763+
String script =
764+
"node {\n" +
765+
cpsScriptUsernamePasswordCredentialTestHttpRequest("CONTROLLER NODE") +
766+
"}\n";
767+
proj.setDefinition(new CpsFlowDefinition(script, false));
768+
769+
// Execute the build
770+
WorkflowRun run = proj.scheduleBuild2(0).get();
771+
if (verbosePipelines) System.out.println(getLogAsStringPlaintext(run));
772+
773+
// Check expectations
774+
j.assertBuildStatus(Result.SUCCESS, run);
775+
// Got to the end?
776+
j.assertLogContains("HTTP Request Plugin Response: ", run);
777+
}
778+
779+
/**
780+
* Check that Username credentials are usable with pipeline script
781+
* running on a remote {@code node{}} with separate JVM (e.g.
782+
* check that remoting and credential snapshot work properly).
783+
*/
784+
@Test
785+
@Issue("JENKINS-70101")
786+
void testUsernamePasswordHttpRequestOnNodeRemote() throws Exception {
787+
// Check that credentials are usable with pipeline script
788+
// running on a remote node{} with separate JVM (check
789+
// that remoting/snapshot work properly)
790+
assumeTrue(this.setupAgent() == true, "This test needs a separate build agent");
791+
792+
prepareUsernamePassword();
793+
794+
// Configure the build to use the credential
795+
WorkflowJob proj = j.jenkins.createProject(WorkflowJob.class, "proj");
796+
String script =
797+
"node(\"" + agentLabelString + "\") {\n" +
798+
cpsScriptUsernamePasswordCredentialTestHttpRequest("REMOTE NODE") +
799+
"}\n";
800+
proj.setDefinition(new CpsFlowDefinition(script, false));
801+
802+
// Execute the build
803+
WorkflowRun run = proj.scheduleBuild2(0).get();
804+
if (verbosePipelines) System.out.println(getLogAsStringPlaintext(run));
805+
806+
// Check expectations
807+
j.assertBuildStatus(Result.SUCCESS, run);
808+
// Got to the end?
809+
j.assertLogContains("HTTP Request Plugin Response: ", run);
810+
}
551811
}

0 commit comments

Comments
 (0)