Skip to content

Revise tests for certificate authentication on ssh agents#231

Merged
MarkEWaite merged 15 commits intojenkinsci:masterfrom
jimklimov:JENKINS-70101-tests
Mar 25, 2026
Merged

Revise tests for certificate authentication on ssh agents#231
MarkEWaite merged 15 commits intojenkinsci:masterfrom
jimklimov:JENKINS-70101-tests

Conversation

@jimklimov
Copy link
Copy Markdown
Contributor

@jimklimov jimklimov commented Jan 5, 2026

Context:

A long-standing PR jenkinsci/credentials-plugin#391 aims to fix (Certificate) Credential snapshot() implementation to allow remote build agents to correctly use a Credential (there was originally a mismatch of encrypted bytes and a random KEY specific to each JVM run-time instance to manipulate them).

The original problem was noticed with use of this HTTP Request plugin, so some of the tests in that PR revolved around making sure the multi-node pipelines using this plugin would work correctly.

Reviewers of that PR insisted on not adding dependencies (even test ones) unless strictly required, so this PR is posted to shift the weight of those tests into HTTP Request plugin: it already has dependencies on Credentials Plugin anyway, it does one way or another suffer practical usability issues (even through no fault of its own), and an earlier version of those tests (running only in Jenkins controller's local JVM) was imported and merged years ago as part of PR #120 for a loosely related issue.

The chain of commits in this PR modifies just the HttpRequestStepCredentialsTest file of all the Java code, first to better javadoc its existing data and methods, and later to import code from jenkinsci/credentials-plugin#391 (most of which would be dropped there) as needed to set up the remote build agent and run test cases with pipelines using it. Beside certificate credentials, this also adds testing of username credentials for good measure.

Tests with a remote agent are currently optionally blocked away by static private Boolean credentialsPluginDoesSnapshotsRight which is true if class loader knows about com.cloudbees.plugins.credentials.impl.CertificateCredentialsSnapshotTaker (it is added in jenkinsci/credentials-plugin#391 and so means using a build of Credentials Plugin with the fix for JENKINS-70101 issue) -- if the ultimate fix in that other PR ends up differently identifiable, or later code evolution changes it, this part may need revision. Actually when some fix gets merged over there, a small commit to HTTP Request over here to require a certain minimum version of Credentials plugin and to drop the complexity of this check in the test would also be a decent solution.

  • For now, developers tinkering with the test can redefine credentialsPluginTestRemoteAlways = true to do run remote tests and maybe fail trying, to confirm whether the original issue was resolved or not. I thought about configuring it via a JVM property or environment variable, but chose against adding a complex feature into this hopefully temporary fix.

I have verified locally that:

  • by default the code committed into this PR branch passes the test suite, skipping the two test cases that should run on remote nodes (credentialsPluginDoesSnapshotsRight is detected as false using the default Credentials Plugin version, developer toggle credentialsPluginTestRemoteAlways is hard-coded as false)
  • if I flip credentialsPluginTestRemoteAlways to true, the testCertHttpRequestOnNodeRemote() fails (as expected so far) with
java.io.IOException: Remote call on slave0 failed
hudson.remoting.ProxyException: javax.crypto.BadPaddingException: Given final block not properly padded. Such issues can arise if a bad key is used during decryption.

... but testUsernamePasswordHttpRequestOnNodeRemote() actually passes (is not impacted by botched transfer of SecretBytes)

  • I am so far struggling to ensure that the custom build of the fixed Credentials plugin gets used, to test that these HTTP Request tests would pass.

…ile names [JENKINS-70101]

Signed-off-by: Jim Klimov <jimklimov+jenkinsci@gmail.com>
… shared property declarations and group test-case families [JENKINS-70101]

Signed-off-by: Jim Klimov <jimklimov+jenkinsci@gmail.com>
… refactor to use verbosePipelines test class property [JENKINS-70101]

Signed-off-by: Jim Klimov <jimklimov+jenkinsci@gmail.com>
… refactor to optionally juggle withReentrability and to debug-trace withLocalCertLookup [JENKINS-70101]

Import and adapt code evicted from jenkinsci/credentials-plugin#391

Signed-off-by: Jim Klimov <jimklimov+jenkinsci@gmail.com>
…updated cpsScriptCredentialTestHttpRequest() helper, and import their siblings from credentials-plugin#391 [JENKINS-70101]

Signed-off-by: Jim Klimov <jimklimov+jenkinsci@gmail.com>
…helper: make use of runnerTag argument [JENKINS-70101]

Signed-off-by: Jim Klimov <jimklimov+jenkinsci@gmail.com>
…helper: report cred ID if drilling withLocalCertLookup [JENKINS-70101]

Signed-off-by: Jim Klimov <jimklimov+jenkinsci@gmail.com>
…onally) verify that credentials pass over to remote agents correctly [JENKINS-70101]

Signed-off-by: Jim Klimov <jimklimov+jenkinsci@gmail.com>
@jimklimov jimklimov requested a review from a team as a code owner January 5, 2026 14:59
@jimklimov
Copy link
Copy Markdown
Contributor Author

UPDATE: Got it to run and pass all defined test cases with custom-built Credentials plugin (which requires a recent ecosystem of Jenkins core and dependency plugins), with auto-detection of the build with fixed JENKINS-70101 working as expected:

  • fixed back the default credentialsPluginTestRemoteAlways = false
  • modified pom.xml to require at least Jenkins 2.504.3 and BOM 5804.v80587a_38d937
  • added https://repo.jenkins-ci.org/incrementals as a new entry in repository and pluginRepository
  • added <version>1501.v00f26696ff79</version> to dependency for credentials
  • ran mvn clean and began mvn package -U to fetch everything it needs per updated requirements
    ** Might suffice to run the mvn test -Dtest="HttpRequestStepCredentialsTest" (anyhow I regularly do the equivalent in IDEA UI)?

What failed was a too-stone-age approach:

  • just modifying Jenkins core version requirement, updating and placing a build of that other PR into target/test-classes/test-dependencies/ (and its dependencies so the minimal versions required by that build of Credentials are satisfied), e.g.:
:; cp ../credentials-plugin/target/jenkins-for-test/WEB-INF/detached-plugins/*.?pi target/test-classes/test-dependencies/
:; cp ../credentials-plugin/target/test-classes/test-dependencies/*.?pi target/test-classes/test-dependencies/
:; wget https://repo.jenkins-ci.org/incrementals/org/jenkins-ci/plugins/credentials/1501.v00f26696ff79/credentials-1501.v00f26696ff79.hpi -O target/test-classes/test-dependencies/credentials.hpi

...with this, the run-time unpacked test area seems to be the same as when the version is mentioned in pom.xml and uses files that have the new class in the plugin, but in fact the test Jenkins instance loads something else from somewhere else, it seems.

…(), it seems to be more reliable [JENKINS-70101]

Signed-off-by: Jim Klimov <jimklimov+jenkinsci@gmail.com>
jimklimov added a commit to jimklimov/credentials-plugin that referenced this pull request Jan 5, 2026
…t code for use of HTTP Request plugin with Credentials [JENKINS-70101]

Tests to make sure the complex call stack works properly are offloaded into
that plugin, see jenkinsci/http-request-plugin#231

Signed-off-by: Jim Klimov <jimklimov+jenkinsci@gmail.com>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates HttpRequestStepCredentialsTest to exercise credentials usage in http-request Pipeline steps across controller, local node {} execution, and (optionally) a remoting agent JVM to validate behavior related to JENKINS-70101.

Changes:

  • Expanded and refactored the test class with more helper methods/Javadoc and additional pipeline-script generation.
  • Added conditional remote-agent tests (gated by detecting a credentials-plugin fix) for certificate credentials and username/password credentials.
  • Introduced agent setup logic and additional credential preparation helpers.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +547 to +556
// Can be used to skip optional tests if we know we could not set up an agent
if (agent == null)
return false;
return agentUsable;
}

private Boolean setupAgent() throws OutOfMemoryError, Exception {
if (isAvailableAgent())
return true;

Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agentUsable is initialized as null and isAvailableAgent() returns it directly when agent != null. Since setupAgent() uses if (isAvailableAgent()), a null value would trigger auto-unboxing and a NullPointerException. Consider making agentUsable a primitive boolean (default false) or have isAvailableAgent() return Boolean.TRUE.equals(agentUsable).

Copilot uses AI. Check for mistakes.
Comment on lines +554 to +576
if (isAvailableAgent())
return true;

// See how credentialsPluginTestRemoteAlways is determined above
// and revise if the ultimately merged fix that started as
// https://github.com/jenkinsci/credentials-plugin/pull/391
// gets changed before the merge or later on...
String msg_70101 = "This test needs a version of credentials-plugin with a fix for JENKINS-70101, and that does not seem to be deployed here";
if (!credentialsPluginTestRemoteAlways)
assumeTrue(credentialsPluginDoesSnapshotsRight, msg_70101);

// else: credentialsPluginTestRemoteAlways, even if we fail
if (!credentialsPluginDoesSnapshotsRight) {
System.err.println("WARNING: " + msg_70101 + "; this test run was configured to try remote agents anyway");
// return false;
}

// Note we anticipate this might fail e.g. due to system resources;
// it should not block the whole test suite from running
// (we would just dynamically skip certain test cases)
try {
// Define a "Permanent Agent"
Label agentLabel = Label.get(agentLabelString);
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setupAgent() is intended to allow remote-agent tests to be skipped when resources are unavailable, but it still declares throws Exception and only catches Descriptor.FormException | NullPointerException. Failures like IOException, InterruptedException, or other runtime exceptions will fail the test rather than returning false for the surrounding assumeTrue(...). Consider catching broader exceptions around agent creation/connection, setting agentUsable=false, and returning false so the tests are reliably skipped instead of failing the suite.

Copilot uses AI. Check for mistakes.
Comment on lines +593 to +606
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 " +
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agent readiness is currently detected by polling agent.getComputer().getLog() and matching the hard-coded string "Agent successfully connected and online" with Thread.sleep retries. This is brittle (log messages can change) and slows the test suite. Prefer checking agent.getComputer().isOnline() / waitUntilOnline() (or JenkinsRule helpers) and waiting on a condition rather than parsing logs.

Copilot uses AI. Check for mistakes.
Comment on lines +567 to +610
System.err.println("WARNING: " + msg_70101 + "; this test run was configured to try remote agents anyway");
// return false;
}

// Note we anticipate this might fail e.g. due to system resources;
// it should not block the whole test suite from running
// (we would just dynamically skip certain test cases)
try {
// Define a "Permanent Agent"
Label agentLabel = Label.get(agentLabelString);
agent = j.createOnlineSlave(agentLabel);
agent.setNodeDescription("Worker in another JVM, remoting used");
agent.setNumExecutors(1);
agent.setMode(Node.Mode.EXCLUSIVE);
///agent.setRetentionStrategy(new RetentionStrategy.Always());

/*
// Add node envvars
List<Entry> env = new ArrayList<Entry>();
env.add(new Entry("key1","value1"));
env.add(new Entry("key2","value2"));
EnvironmentVariablesNodeProperty envPro = new EnvironmentVariablesNodeProperty(env);
agent.getNodeProperties().add(envPro);
*/

String agentLog = null;
agentUsable = false;
for (long i = 0; i < 5; i++) {
Thread.sleep(1000);
agentLog = agent.getComputer().getLog();
if (i == 2 && (agentLog == null || agentLog.isEmpty())) {
// Give it a little time to autostart, then kick it up if needed:
agent.getComputer().connect(true); // "always" should have started it; avoid duplicate runs
}
if (agentLog != null && agentLog.contains("Agent successfully connected and online")) {
agentUsable = true;
break;
}
}
System.out.println("Spawned build agent " +
"usability: " + agentUsable.toString() +
"; connection log:" + (agentLog == null ? " <null>" : "\n" + agentLog));
} catch (Descriptor.FormException | NullPointerException e) {
agentUsable = false;
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setupAgent() prints warnings and connection logs directly to System.err / System.out unconditionally. This can add a lot of noise to CI logs even when verbosePipelines is false. Consider guarding these prints behind verbosePipelines (or using a Logger with an appropriate level) so normal runs stay quiet.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor

@MarkEWaite MarkEWaite left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@MarkEWaite MarkEWaite changed the title Revise tests for issue JENKINS-70101 Revise tests for certificate authentication on ssh agents Mar 25, 2026
@MarkEWaite MarkEWaite merged commit 556fb38 into jenkinsci:master Mar 25, 2026
17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants