@@ -15,7 +15,6 @@ import org.eclipse.jgit.util.FileUtils
1515import java.nio.file.Files
1616import java.nio.file.Path
1717import java.nio.file.Paths
18-
1918import org.slf4j.Logger
2019import org.slf4j.LoggerFactory
2120/**
@@ -26,6 +25,51 @@ class GitManager {
2625 private static final Logger logger = LoggerFactory . getLogger(GitManager . class);
2726
2827 public static final String REMOTE_NAME = " origin"
28+
29+ /**
30+ * Executes a closure with the thread context classloader set to the plugin's classloader.
31+ * Required because Rundeck isolates plugins with separate classloaders, and JGit's
32+ * TranslationBundle mechanism uses ResourceBundle.getBundle() which relies on the
33+ * thread context classloader to find resource bundles like SshdText.
34+ * Also ensures Ed25519 key support works correctly on Java 15+.
35+ */
36+ private static <T> T withPluginClassLoader (Closure<T> closure ) {
37+ ClassLoader original = Thread . currentThread(). getContextClassLoader()
38+ try {
39+ Thread . currentThread(). setContextClassLoader(GitManager . class. getClassLoader())
40+ ensureEdDSASupport()
41+ return closure. call()
42+ } finally {
43+ Thread . currentThread(). setContextClassLoader(original)
44+ }
45+ }
46+
47+ private static volatile boolean eddsaChecked = false
48+
49+ /**
50+ * Ensures Ed25519 key support works correctly by removing the external EdDSA provider
51+ * if present. On Java 15+, Ed25519 is supported natively by the JDK. The external
52+ * net.i2p.crypto.eddsa provider causes ClassNotFoundException due to Rundeck's plugin
53+ * classloader isolation, so it must be removed to let SSHD use native support.
54+ */
55+ private static void ensureEdDSASupport () {
56+ if (eddsaChecked) return
57+ try {
58+ int javaVersion = Runtime . version(). feature()
59+ if (javaVersion >= 15 ) {
60+ if (java.security.Security . getProvider(" EdDSA" ) != null ) {
61+ java.security.Security . removeProvider(" EdDSA" )
62+ logger. info(" Removed external EdDSA provider to use native Java {} Ed25519 support" , javaVersion)
63+ }
64+ } else {
65+ logger. info(" Java {} does not have native Ed25519 support. Ed25519 keys require Java 15+." , javaVersion)
66+ }
67+ eddsaChecked = true
68+ } catch (Exception e) {
69+ logger. warn(" EdDSA provider check failed: {}" , e. message)
70+ }
71+ }
72+
2973 Git git
3074 String branch
3175 String fileName
@@ -134,7 +178,7 @@ class GitManager {
134178
135179 try {
136180 setupTransportAuthentication(sshConfig, cloneCommand, this . gitURL)
137- git = cloneCommand. call()
181+ git = withPluginClassLoader { cloneCommand. call() }
138182 } catch (Exception e) {
139183 e. printStackTrace()
140184 logger. debug(" Failed cloning the repository from ${ this.gitURL} : ${ e.message} " , e)
@@ -151,7 +195,7 @@ class GitManager {
151195
152196 try {
153197 setupTransportAuthentication(sshConfig, pullCommand, this . gitURL)
154- PullResult result = pullCommand. call()
198+ PullResult result = withPluginClassLoader { pullCommand. call() }
155199 if (! result. isSuccessful()) {
156200 logger. info(" Pull is not successful." )
157201 } else {
@@ -171,7 +215,7 @@ class GitManager {
171215
172216 try {
173217 setupTransportAuthentication(sshConfig, pushCommand, this . gitURL)
174- pushCommand. call()
218+ withPluginClassLoader { pushCommand. call() }
175219 logger. info(" Push is not successful." )
176220 } catch (Exception e) {
177221 e. printStackTrace()
@@ -222,17 +266,17 @@ class GitManager {
222266 }
223267
224268 URIish u = new URIish (url);
225- logger. debug(" transport url ${ u } , scheme ${ u .scheme} , user ${ u.user} " )
269+ logger. debug(" setupTransportAuth: url={ }, scheme={}, user={} " , u, u . scheme, u. user)
226270 if ((u. scheme == null || u. scheme == ' ssh' ) && u. user && (sshPrivateKeyPath || sshPrivateKey)) {
227271
228272 byte [] keyData
229273 if (sshPrivateKeyPath) {
230- logger. debug(" using ssh private key path ${ sshPrivateKeyPath } " )
274+ logger. debug(" Using SSH private key from filesystem path " )
231275 Path path = Paths . get(sshPrivateKeyPath);
232276 keyData = Files . readAllBytes(path);
233277
234278 } else if (sshPrivateKey) {
235- logger. debug(" using ssh private key" )
279+ logger. debug(" Using SSH private key from Key Storage " )
236280 keyData = sshPrivateKey. getBytes()
237281 }
238282
@@ -251,7 +295,7 @@ class GitManager {
251295 PullResult gitPull (Git git1 = null ) {
252296 def pullCommand = (git1 ?: git). pull(). setRemote(REMOTE_NAME ). setRemoteBranchName(branch)
253297 setupTransportAuthentication(sshConfig, pullCommand)
254- pullCommand. call()
298+ withPluginClassLoader { pullCommand. call() }
255299 }
256300
257301 def gitCommitAndPush () {
@@ -277,7 +321,7 @@ class GitManager {
277321
278322 def push
279323 try {
280- push = pushb. call()
324+ push = withPluginClassLoader { pushb. call() }
281325 } catch (Exception e) {
282326 logger. debug(" Failed push to remote: ${ e.message} " , e)
283327 throw new Exception (" Failed push to remote: ${ e.message} " , e)
0 commit comments