From b78ed45a2538da89381d665280c980eecabaccd1 Mon Sep 17 00:00:00 2001 From: Russ Robinson Date: Thu, 22 Jun 2023 13:17:37 -0400 Subject: [PATCH 01/10] add pty into session and more sudo command logging --- src/main/java/com/plugin/sshjplugin/model/SSHJExec.java | 3 +++ src/main/java/com/plugin/sshjplugin/sudo/SudoCommand.java | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/src/main/java/com/plugin/sshjplugin/model/SSHJExec.java b/src/main/java/com/plugin/sshjplugin/model/SSHJExec.java index 8462c57..3caf74d 100644 --- a/src/main/java/com/plugin/sshjplugin/model/SSHJExec.java +++ b/src/main/java/com/plugin/sshjplugin/model/SSHJExec.java @@ -53,6 +53,7 @@ public void execute(SSHClient ssh) { pluginLogger.log(3, "["+getPluginName()+"] starting session" ); session = ssh.startSession(); + session.allocateDefaultPTY(); pluginLogger.log(3, "["+getPluginName()+"] setting environments" ); @@ -84,6 +85,8 @@ public void execute(SSHClient ssh) { } String sudoPassword = this.getSshjConnection().getSudoPassword(sudoPasswordPath); + pluginLogger.log(3, "["+getPluginName()+"] using sudoPassword value of: " + sudoPassword ); + SudoCommand sudoCommandRunner = new SudoCommandBuilder() .sudoPromptPattern(this.getSshjConnection().getSudoPromptPattern()) .sudoPassword(sudoPassword) diff --git a/src/main/java/com/plugin/sshjplugin/sudo/SudoCommand.java b/src/main/java/com/plugin/sshjplugin/sudo/SudoCommand.java index b895ee8..394ebd5 100644 --- a/src/main/java/com/plugin/sshjplugin/sudo/SudoCommand.java +++ b/src/main/java/com/plugin/sshjplugin/sudo/SudoCommand.java @@ -64,6 +64,8 @@ public void setLogger(PluginLogger logger) { public String runSudoCommand(String command) throws IOException { + logger.log(3, "Entered runSudoCommand"); + Expect expect = new ExpectBuilder() .withOutput(outputStream) .withInputs(inputStream, errorStream) @@ -77,10 +79,13 @@ public String runSudoCommand(String command) throws IOException { .withTimeout(30000, TimeUnit.SECONDS) .build(); + logger.log(3, "Looking for pattern :" + PROMPT_PATTERN); expect.expect(regexp(PROMPT_PATTERN)); + //expect.sendLine("stty -echo"); //expect.interact(); + logger.log(3, "Issuing command :" + command); expect.sendLine(command); logger.log(3, "SUDO command enabled"); @@ -90,6 +95,7 @@ public String runSudoCommand(String command) throws IOException { expect.expect(contains(sudoPromptPattern)); expect.sendLine(sudoPassword); + logger.log(3, "Looking for pattern :" + PROMPT_PATTERN); expect.expect(regexp(PROMPT_PATTERN)); expect.sendLine("echo $?"); From e219b502fee8a5ccfd3e31abdb2c13be12a4a7c4 Mon Sep 17 00:00:00 2001 From: Luis Toledo Date: Fri, 16 Jun 2023 11:52:27 -0400 Subject: [PATCH 02/10] remove use of temporary file to authenticate with password improve tests add new test --- .../sshjplugin/SSHJNodeExecutorPlugin.java | 6 +- .../sshjplugin/model/SSHJAuthentication.java | 56 ++-- .../sshjplugin/model/SSHJConnection.java | 2 + .../model/SSHJConnectionParameters.java | 9 +- .../sshjplugin/util/PropertyResolver.java | 19 +- .../SSHJNodeExecutorPluginSpec.groovy | 297 +++++++++++++++++- 6 files changed, 347 insertions(+), 42 deletions(-) diff --git a/src/main/java/com/plugin/sshjplugin/SSHJNodeExecutorPlugin.java b/src/main/java/com/plugin/sshjplugin/SSHJNodeExecutorPlugin.java index d39c88c..a966337 100644 --- a/src/main/java/com/plugin/sshjplugin/SSHJNodeExecutorPlugin.java +++ b/src/main/java/com/plugin/sshjplugin/SSHJNodeExecutorPlugin.java @@ -256,9 +256,9 @@ public NodeExecutorResult executeCommand(ExecutionContext context, String[] comm sshexec.execute(sshClient); success = true; } catch (Exception e) { - final ExtractFailure extractJschFailure = extractFailure(e, node, commandtimeout, contimeout, context.getFramework()); - errormsg = extractJschFailure.getErrormsg(); - failureReason = extractJschFailure.getReason(); + final ExtractFailure extractFailure = extractFailure(e, node, commandtimeout, contimeout, context.getFramework()); + errormsg = extractFailure.getErrormsg(); + failureReason = extractFailure.getReason(); context.getExecutionListener().log( 3, String.format( diff --git a/src/main/java/com/plugin/sshjplugin/model/SSHJAuthentication.java b/src/main/java/com/plugin/sshjplugin/model/SSHJAuthentication.java index 9440887..986ecea 100644 --- a/src/main/java/com/plugin/sshjplugin/model/SSHJAuthentication.java +++ b/src/main/java/com/plugin/sshjplugin/model/SSHJAuthentication.java @@ -1,17 +1,22 @@ package com.plugin.sshjplugin.model; import com.dtolabs.rundeck.plugins.PluginLogger; +import com.dtolabs.utils.Streams; import com.plugin.sshjplugin.SSHJBuilder; import net.schmizz.sshj.SSHClient; -import net.schmizz.sshj.userauth.keyprovider.KeyProvider; -import java.io.File; -import java.io.IOException; +import net.schmizz.sshj.common.Factory; +import net.schmizz.sshj.userauth.keyprovider.FileKeyProvider; +import net.schmizz.sshj.userauth.keyprovider.KeyFormat; +import net.schmizz.sshj.userauth.keyprovider.KeyProviderUtil; +import net.schmizz.sshj.userauth.password.PasswordUtils; + +import java.io.*; public class SSHJAuthentication { SSHJConnection.AuthenticationType authenticationType; String username; String password; - String privateKeyFile; + String privateKeyContent; String passphrase; PluginLogger logger; SSHJConnection connectionParameters; @@ -28,35 +33,34 @@ void authenticate(final SSHClient ssh) throws IOException { switch (authenticationType) { case privateKey: - try{ - privateKeyFile = connectionParameters.getPrivateKeyPath(); - logger.log(3, "Authenticating using private key"); + logger.log(3, "Authenticating using private key"); - } catch (IOException e) { - logger.log(0, "Failed to get SSH key: " + e.getMessage()); + String privateKeyPath = connectionParameters.getPrivateKeyStoragePath(); + try{ + privateKeyContent = connectionParameters.getPrivateKeyStorage(privateKeyPath); + } catch (Exception e) { + throw new SSHJBuilder.BuilderException("Failed to read SSH Key Storage stored at path: " + privateKeyPath); } String passphrasePath = connectionParameters.getPrivateKeyPassphraseStoragePath(); try{ passphrase = connectionParameters.getPrivateKeyPassphrase(passphrasePath); - } catch (IOException e) { - logger.log(0, "Failed to read SSH Passphrase stored at path: " + passphrasePath); + } catch (Exception e) { + throw new SSHJBuilder.BuilderException("Failed to read SSH Passphrase stored at path: " + passphrasePath); } - KeyProvider key = null; - if (null != privateKeyFile && !"".equals(privateKeyFile)) { - if (!new File(privateKeyFile).exists()) { - throw new SSHJBuilder.BuilderException("SSH Keyfile does not exist: " + privateKeyFile); - } - logger.log(3, "[sshj-debug] Using ssh keyfile: " + privateKeyFile); - } + KeyFormat format = KeyProviderUtil.detectKeyFileFormat(privateKeyContent,true); + FileKeyProvider keys = Factory.Named.Util.create(ssh.getTransport().getConfig().getFileKeyProviderFactories(), format.toString()); + + logger.log(3, "[sshj-debug] Using ssh keyfile: " + privateKeyPath); if (passphrase == null) { - key = ssh.loadKeys(privateKeyFile); + keys.init(new StringReader(privateKeyContent), null); } else { - key = ssh.loadKeys(privateKeyFile, passphrase); + logger.log(3, "[sshj-debug] Using Passphrase: " + passphrasePath); + keys.init(new StringReader(privateKeyContent), PasswordUtils.createOneOff(passphrase.toCharArray())); } - ssh.authPublickey(username, key); + ssh.authPublickey(username, keys); break; case password: String passwordPath = connectionParameters.getPasswordStoragePath(); @@ -65,15 +69,11 @@ void authenticate(final SSHClient ssh) throws IOException { } try{ password = connectionParameters.getPassword(passwordPath); - } catch (IOException e) { - logger.log(0, "Failed to read SSH Password stored at path: " + passwordPath); + } catch (Exception e) { + throw new SSHJBuilder.BuilderException("Failed to read SSH Password stored at path: " + passwordPath); } - if (password != null) { - ssh.authPassword(username, password); - }else{ - throw new SSHJBuilder.BuilderException("SSH password wasn't set, please define a password"); - } + ssh.authPassword(username, password); break; } } diff --git a/src/main/java/com/plugin/sshjplugin/model/SSHJConnection.java b/src/main/java/com/plugin/sshjplugin/model/SSHJConnection.java index 1d5cfa3..95935bf 100644 --- a/src/main/java/com/plugin/sshjplugin/model/SSHJConnection.java +++ b/src/main/java/com/plugin/sshjplugin/model/SSHJConnection.java @@ -21,6 +21,8 @@ static enum AuthenticationType { InputStream getPrivateKeyStorageData(String path); + String getPrivateKeyStorage(String path) throws IOException; + String getPasswordStoragePath(); String getPassword(String path) throws IOException; diff --git a/src/main/java/com/plugin/sshjplugin/model/SSHJConnectionParameters.java b/src/main/java/com/plugin/sshjplugin/model/SSHJConnectionParameters.java index 3c45c44..ba8ff13 100644 --- a/src/main/java/com/plugin/sshjplugin/model/SSHJConnectionParameters.java +++ b/src/main/java/com/plugin/sshjplugin/model/SSHJConnectionParameters.java @@ -69,7 +69,6 @@ public String getPrivateKeyPath() throws IOException { @Override public String getPrivateKeyStoragePath(){ - String path = propertyResolver.resolve(SSHJNodeExecutorPlugin.NODE_ATTR_SSH_KEY_RESOURCE); if (path == null && framework.hasProperty(Constants.SSH_KEYRESOURCE_PROP)) { //return default framework level @@ -85,14 +84,18 @@ public String getPrivateKeyStoragePath(){ @Override public InputStream getPrivateKeyStorageData(String path){ try { - InputStream sshKey = propertyResolver.getPrivateKeyStorageData(path); - return sshKey; + return propertyResolver.getPrivateKeyStorageData(path); } catch (IOException e) { throw new RuntimeException(e); } } + @Override + public String getPrivateKeyStorage(String path) throws IOException { + return propertyResolver.getPrivateKeyStorage(path); + } + String getPrivateKeyfilePath() { String path = propertyResolver.resolve(SSHJNodeExecutorPlugin.NODE_ATTR_SSH_KEYPATH); if (path == null && framework.hasProperty(Constants.SSH_KEYPATH_PROP)) { diff --git a/src/main/java/com/plugin/sshjplugin/util/PropertyResolver.java b/src/main/java/com/plugin/sshjplugin/util/PropertyResolver.java index f167258..31c670a 100644 --- a/src/main/java/com/plugin/sshjplugin/util/PropertyResolver.java +++ b/src/main/java/com/plugin/sshjplugin/util/PropertyResolver.java @@ -69,6 +69,22 @@ public InputStream getPrivateKeyStorageData(String path) throws IOException { } + public String getPrivateKeyStorage(String path) throws IOException { + //expand properties in path + if (path != null && path.contains("${")) { + path = DataContextUtils.replaceDataReferencesInString(path, context.getDataContext()); + } + if (null == path) { + return null; + } + + ResourceMeta contents = context.getStorageTree().getResource(path).getContents(); + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + contents.writeContent(byteArrayOutputStream); + return byteArrayOutputStream.toString(); + + } + public String getStoragePath(String property) { String path = resolve(property); @@ -83,8 +99,7 @@ public String getPasswordFromPath(String path) throws IOException { ResourceMeta contents = context.getStorageTree().getResource(path).getContents(); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); contents.writeContent(byteArrayOutputStream); - String password = new String(byteArrayOutputStream.toByteArray()); - return password; + return byteArrayOutputStream.toString(); } public String nonBlank(final String input) { diff --git a/src/test/groovy/com/plugin/sshjplugin/SSHJNodeExecutorPluginSpec.groovy b/src/test/groovy/com/plugin/sshjplugin/SSHJNodeExecutorPluginSpec.groovy index 34b63dd..6cd3ccf 100644 --- a/src/test/groovy/com/plugin/sshjplugin/SSHJNodeExecutorPluginSpec.groovy +++ b/src/test/groovy/com/plugin/sshjplugin/SSHJNodeExecutorPluginSpec.groovy @@ -10,6 +10,7 @@ import com.dtolabs.rundeck.core.storage.ResourceMeta import com.dtolabs.rundeck.core.storage.StorageTree import com.dtolabs.rundeck.core.utils.PropertyLookup import net.schmizz.sshj.SSHClient +import net.schmizz.sshj.connection.channel.direct.Session import net.schmizz.sshj.transport.Transport import org.rundeck.storage.api.Resource import spock.lang.Specification @@ -20,7 +21,6 @@ class SSHJNodeExecutorPluginSpec extends Specification { def getContext(Properties properties,def rundeckFramework, def logger) { - def dataContext = [ config: ["RD_TEST": "Value"] ] @@ -38,7 +38,7 @@ class SSHJNodeExecutorPluginSpec extends Specification { getResource('keys/node.key') >> Mock(Resource) { getContents() >> Mock(ResourceMeta) { writeContent(_) >> { args -> - args[0].write('test.'.bytes) + args[0].write('-----BEGIN OPENSSH PRIVATE KEY-----'.bytes) 7L } } @@ -102,10 +102,25 @@ class SSHJNodeExecutorPluginSpec extends Specification { ]) when: - plugin.executeCommand(context, command, node) + def result = plugin.executeCommand(context, command, node) then: + 1 * client.connect(_) + 1 * client.isConnected() >> true + 1 * client.startSession()>>Mock(Session){ + exec(_)>>Mock(Session.Command){ + getExitStatus()>>0 + getInputStream()>>Mock(InputStream){ + read(_)>>-1 + } + getErrorStream()>>Mock(InputStream){ + read(_)>>-1 + } + } + } 1 * logger.log(3, "Authenticating using password: keys/password") + result!=null + result.success } @@ -145,10 +160,25 @@ class SSHJNodeExecutorPluginSpec extends Specification { ]) when: - plugin.executeCommand(context, command, node) + def result = plugin.executeCommand(context, command, node) then: + 1 * client.connect(_) + 1 * client.isConnected() >> true + 1 * client.startSession()>>Mock(Session){ + exec(_)>>Mock(Session.Command){ + getExitStatus()>>0 + getInputStream()>>Mock(InputStream){ + read(_)>>-1 + } + getErrorStream()>>Mock(InputStream){ + read(_)>>-1 + } + } + } 1 * logger.log(3, "Authenticating using private key") + result!=null + result.success } @@ -190,10 +220,25 @@ class SSHJNodeExecutorPluginSpec extends Specification { ]) when: - plugin.executeCommand(context, command, node) + def result = plugin.executeCommand(context, command, node) then: + 1 * client.connect(_) + 1 * client.isConnected() >> true + 1 * client.startSession()>>Mock(Session){ + exec(_)>>Mock(Session.Command){ + getExitStatus()>>0 + getInputStream()>>Mock(InputStream){ + read(_)>>-1 + } + getErrorStream()>>Mock(InputStream){ + read(_)>>-1 + } + } + } 1 * logger.log(3, "Authenticating using password: keys/password") + result!=null + result.success } @@ -233,10 +278,250 @@ class SSHJNodeExecutorPluginSpec extends Specification { ]) when: - plugin.executeCommand(context, command, node) + def result = plugin.executeCommand(context, command, node) then: + 1 * client.connect(_) + 1 * client.isConnected() >> true + 1 * client.startSession()>>Mock(Session){ + exec(_)>>Mock(Session.Command){ + getExitStatus()>>0 + getInputStream()>>Mock(InputStream){ + read(_)>>-1 + } + getErrorStream()>>Mock(InputStream){ + read(_)>>-1 + } + } + } 1 * logger.log(3, "Authenticating using private key") + result!=null + result.success + + + + } + + + def "error getting key"(){ + + given: + + String[] command = ["ls -lrt"] + + def logger = Mock(ExecutionListener) { + createOverride() >> Mock(ExecutionListenerOverride) + } + + def rundeckFramework = Mock(IRundeckProject) + def properties = new Properties() + properties.setProperty("fwkprop","fwkvalue") + + def dataContext = [ + config: ["RD_TEST": "Value"] + ] + + def storage = Mock(StorageTree) { + getResource('keys/password') >> {throw new Exception("Cannot get password")} + getResource('keys/node.key') >> {throw new Exception("Cannot get key")} + } + + def framework = Mock(Framework) { + getFrameworkProjectMgr() >> Mock(ProjectManager) { + getFrameworkProject(_) >> rundeckFramework + } + getPropertyLookup() >> PropertyLookup.create(properties) + getProjectManager() >> Mock(ProjectManager) { + getFrameworkProject(_) >> rundeckFramework + } + } + + def context = ExecutionContextImpl.builder() + .framework(framework) + .executionListener(logger) + .storageTree(storage) + .dataContext(new BaseDataContext(dataContext)) + .frameworkProject("test") + .build() + + SSHClient client = Mock(SSHClient){ + getTransport()>>Mock(Transport){ + getConfig()>>SSHJDefaultConfig.init().getConfig() + } + } + + def plugin = new SSHJNodeExecutorPlugin() + plugin.sshClient = client + + def node = new NodeEntryImpl("test") + node.setAttributes(["username":"test", + "osFamily":"linux", + "hostname":"localhost", + "ssh-connect-timeout":"3", + "ssh-command-timeout":"3", + "ssh-authentication":"privateKey", + "ssh-key-storage-path":"keys/node.key", + "sudo-command-enabled":"true" + ]) + + when: + def result = plugin.executeCommand(context, command, node) + + then: + !result.success + result.failureMessage=="Failed to read SSH Key Storage stored at path: keys/node.key" + + + } + + + def "error getting password"(){ + + given: + + String[] command = ["ls -lrt"] + + def logger = Mock(ExecutionListener) { + createOverride() >> Mock(ExecutionListenerOverride) + } + + def rundeckFramework = Mock(IRundeckProject) + def properties = new Properties() + properties.setProperty("fwkprop","fwkvalue") + + def dataContext = [ + config: ["RD_TEST": "Value"] + ] + + def storage = Mock(StorageTree) { + getResource('keys/password') >> {throw new Exception("Cannot get password")} + getResource('keys/node.key') >> {throw new Exception("Cannot get key")} + } + + def framework = Mock(Framework) { + getFrameworkProjectMgr() >> Mock(ProjectManager) { + getFrameworkProject(_) >> rundeckFramework + } + getPropertyLookup() >> PropertyLookup.create(properties) + getProjectManager() >> Mock(ProjectManager) { + getFrameworkProject(_) >> rundeckFramework + } + } + + def context = ExecutionContextImpl.builder() + .framework(framework) + .executionListener(logger) + .storageTree(storage) + .dataContext(new BaseDataContext(dataContext)) + .frameworkProject("test") + .build() + + SSHClient client = Mock(SSHClient){ + getTransport()>>Mock(Transport){ + getConfig()>>SSHJDefaultConfig.init().getConfig() + } + } + + def plugin = new SSHJNodeExecutorPlugin() + plugin.sshClient = client + + def node = new NodeEntryImpl("test") + node.setAttributes(["username":"test", + "osFamily":"linux", + "hostname":"localhost", + "ssh-connect-timeout":"3", + "ssh-command-timeout":"3", + "ssh-authentication":"password", + "ssh-password-storage-path":"keys/password", + "sudo-command-enabled":"true" + ]) + + when: + def result = plugin.executeCommand(context, command, node) + + then: + !result.success + result.failureMessage=="Failed to read SSH Password stored at path: keys/password" + + + } + + def "error getting key passphrase"(){ + + given: + + String[] command = ["ls -lrt"] + + def logger = Mock(ExecutionListener) { + createOverride() >> Mock(ExecutionListenerOverride) + } + + def rundeckFramework = Mock(IRundeckProject) + def properties = new Properties() + properties.setProperty("fwkprop","fwkvalue") + + def dataContext = [ + config: ["RD_TEST": "Value"] + ] + + def storage = Mock(StorageTree) { + getResource('keys/password') >> {throw new Exception("Cannot get password")} + getResource('keys/node.key') >> Mock(Resource) { + getContents() >> Mock(ResourceMeta) { + writeContent(_) >> { args -> + args[0].write('test.'.bytes) + 7L + } + } + } + } + + def framework = Mock(Framework) { + getFrameworkProjectMgr() >> Mock(ProjectManager) { + getFrameworkProject(_) >> rundeckFramework + } + getPropertyLookup() >> PropertyLookup.create(properties) + getProjectManager() >> Mock(ProjectManager) { + getFrameworkProject(_) >> rundeckFramework + } + } + + def context = ExecutionContextImpl.builder() + .framework(framework) + .executionListener(logger) + .storageTree(storage) + .dataContext(new BaseDataContext(dataContext)) + .frameworkProject("test") + .build() + + SSHClient client = Mock(SSHClient){ + getTransport()>>Mock(Transport){ + getConfig()>>SSHJDefaultConfig.init().getConfig() + } + } + + def plugin = new SSHJNodeExecutorPlugin() + plugin.sshClient = client + + def node = new NodeEntryImpl("test") + node.setAttributes(["username":"test", + "osFamily":"linux", + "hostname":"localhost", + "ssh-connect-timeout":"3", + "ssh-command-timeout":"3", + "ssh-authentication":"privateKey", + "ssh-key-storage-path":"keys/node.key", + "ssh-key-passphrase-storage-path":"keys/password", + "sudo-command-enabled":"true" + ]) + + when: + def result = plugin.executeCommand(context, command, node) + + then: + !result.success + result.failureMessage=="Failed to read SSH Passphrase stored at path: keys/password" + } From 7c129215f31c623e8042457868db8af48bebc5f6 Mon Sep 17 00:00:00 2001 From: Russ Robinson Date: Wed, 2 Aug 2023 12:00:56 -0400 Subject: [PATCH 03/10] rebasing --- .../SSHJBuilder$BuilderException.class | Bin 0 -> 865 bytes .../com/plugin/sshjplugin/SSHJBuilder.class | Bin 0 -> 8379 bytes .../sshjplugin/SSHJFileCopierPlugin.class | Bin 0 -> 6464 bytes ...SHJNodeExecutorPlugin$ExtractFailure.class | Bin 0 -> 1193 bytes .../sshjplugin/SSHJNodeExecutorPlugin.class | Bin 0 -> 12243 bytes .../sshjplugin/model/SSHJAuthentication.class | Bin 0 -> 5110 bytes .../plugin/sshjplugin/model/SSHJBase.class | Bin 0 -> 1966 bytes .../SSHJConnection$AuthenticationType.class | Bin 0 -> 1324 bytes .../sshjplugin/model/SSHJConnection.class | Bin 0 -> 1439 bytes .../model/SSHJConnectionParameters.class | Bin 0 -> 9115 bytes .../plugin/sshjplugin/model/SSHJExec.class | Bin 0 -> 2363 bytes .../com/plugin/sshjplugin/model/SSHJScp.class | Bin 0 -> 3269 bytes .../plugin/sshjplugin/sudo/SudoCommand.class | Bin 0 -> 4573 bytes .../sshjplugin/sudo/SudoCommandBuilder.class | Bin 0 -> 2440 bytes .../sshjplugin/util/PropertyResolver.class | Bin 0 -> 6611 bytes .../util/SSHJSecretBundleUtil.class | Bin 0 -> 3177 bytes .../SSHJNodeExecutorPluginSpec.groovy | 528 ++++++++++++++++++ 17 files changed, 528 insertions(+) create mode 100644 bin/main/com/plugin/sshjplugin/SSHJBuilder$BuilderException.class create mode 100644 bin/main/com/plugin/sshjplugin/SSHJBuilder.class create mode 100644 bin/main/com/plugin/sshjplugin/SSHJFileCopierPlugin.class create mode 100644 bin/main/com/plugin/sshjplugin/SSHJNodeExecutorPlugin$ExtractFailure.class create mode 100644 bin/main/com/plugin/sshjplugin/SSHJNodeExecutorPlugin.class create mode 100644 bin/main/com/plugin/sshjplugin/model/SSHJAuthentication.class create mode 100644 bin/main/com/plugin/sshjplugin/model/SSHJBase.class create mode 100644 bin/main/com/plugin/sshjplugin/model/SSHJConnection$AuthenticationType.class create mode 100644 bin/main/com/plugin/sshjplugin/model/SSHJConnection.class create mode 100644 bin/main/com/plugin/sshjplugin/model/SSHJConnectionParameters.class create mode 100644 bin/main/com/plugin/sshjplugin/model/SSHJExec.class create mode 100644 bin/main/com/plugin/sshjplugin/model/SSHJScp.class create mode 100644 bin/main/com/plugin/sshjplugin/sudo/SudoCommand.class create mode 100644 bin/main/com/plugin/sshjplugin/sudo/SudoCommandBuilder.class create mode 100644 bin/main/com/plugin/sshjplugin/util/PropertyResolver.class create mode 100644 bin/main/com/plugin/sshjplugin/util/SSHJSecretBundleUtil.class create mode 100644 bin/test/com/plugin/sshjplugin/SSHJNodeExecutorPluginSpec.groovy diff --git a/bin/main/com/plugin/sshjplugin/SSHJBuilder$BuilderException.class b/bin/main/com/plugin/sshjplugin/SSHJBuilder$BuilderException.class new file mode 100644 index 0000000000000000000000000000000000000000..d1856e8c987890df61972835329356706ed12449 GIT binary patch literal 865 zcmb7?+b;t_6vn?()zwwEy477150q$vH{yX1r17BA_tj3?k?pMP1^RoS}gADZ3NP;2zMro zTjynQ;!8&vh!aMSty3#+TdJBr2$V1D;^Ulx8!1xe_71=~RS?HY8JMM}p8( zkV@Rd_N=+!p5 z3B>U9cM&1?Lu-rfu;w~v+Gofc)VzK|(tkhT+key(GOg$+XjFqT1heP_Zdq(g{WQHT zvslz$L`dx_CEQKh@;u=gm?C8Vd?E%#2Z=0rMEo-Van5)&yc%3Bb0m)Kxdu8eIElgJ z)#M0Vv-k#`oTZu~j$K@7KsT=%u+B=YP2}bq=!p{USb6_O7;36Q0_)L-G&3}-KV`t3MsP-h~7sKS)4Ox|8y$o1K7kJXp873#a3 zw3Y3(+>HDVU)K5?GM>VrT~!6?P>A%2ObU-S>FNExlePN1lv~L6+v!cIwBy?R$mN`T zYUOImvpny+TRTqeDoNk%yP5o8NARZLXR@hP)=+-JcIoZv2f0Eb_WwMr7 zBRS+cWNL0vGG5oS!A|CEe}mJXwEFv(=QoqbUXyTx&l%Ur=j^=iwI)fY#eu@DWskv| zB}O~y%`kjdJwnh~i=$3)D;ECWe}mdZ|2$!AS8;!uT&gSNlU zv)#OvV>H^osd>DjG0Y*L#YxGGN2{D!$hda@;c-mE3a3+qyu}lD2XHa2!)5>uobTs$I zp|I4%!C0p7NI=6d$CRa;lF`J*`x4v79~r!PnYl{$s>?Gw(C6K)3a$GhGUL5#=N#W2 zviuE_6~_`pGJ@w6+_Ww3rZ9Q5N_-*El~Pji{5|1Xn$6v#G#C>UeW;?`!9HV>8JSWf z5H0RWRGCvXzlk-;=x*OiZ(=mmDc?Z%sCpt`pTaa9uGZS=g6m~A+uDZ|dQJ2QbtX~e zk`6JfS2!@l$dM`~d6wlR^NyeNY+opMs)^HZIuA-a`GL$Jvm!(7Qp+QpH||?7tmf<` zsj3G?5uh@?D`GC*a~R%i;w`Wg4CbAH5Ys~cFOoc%mp0Ka`5;CJdYt|Y?HdwpjV78| zj#mt%-t-d7%JGX~BgX=c%F^<&II_qa$T6a5+J+*Sa3q*aVeC4tkBPM4+p zmBnK%yf>k7FN!~L4g-uHODn}&UCa8Gx5-Q8ZC{Ge9{vbpdWG`u>6Z?pu$n8fi&fjW z2=!`WChq-K6Pxiig(qLGj8arSP+eaXURnAu!x()G7ut^_;upLnO&u!^{dDxDibZ93 zu}Y4aJI<;K&%DCujT7M&BUbN3ych2`@IEF_P394cnm7;V^ZM##&Sq}lU3O&*A7nPt zh>wnZTQEbm`H+bZ<058J&v$K$k?^2mC8;+J-lmw=IM`9oyY|Hs3@bO}WIsR}Hm*H~KXuxroTWQAoWSBR0vtu(+ zx)Pr>a8+1cj}3H{@7E?irDMOn#doc=zryi+sjy@C3}KXCqsnMG;{YOwPWQ z$$M)Ym#`50Q)Ku$6W7a*6T=;4G>*^XCIdHe%Th2}nfGnhF($r%FEZ~9@kVCz-EOW# zD`k(HP27T83CdzpMm4_yxLzg^&RQ2@a5G4yL`FrJh zaNn3)LC~dP7Am0?6k5ewfn22SH}MrbK)57r7kDYn`%#(SCg`o9A}T; zf+#*jO_{us)~Nl1Ypt|xeA~^jQPdyD4m@n)5vd3!vLYzbNZvnc;_LVZjmGx@Ri>9C zUNo7MK|Am*6W_*n$P-TP9#bc$sykjupU_(0Gx2@=AShGTl(ly>;FU7>Zb~@=k1Nb9 znImY8o#41R%P(u=7%X%p?kmi#ioGr;$%Z03h>*~NJ1gv0X89sOyl-;fS!E*?h3Md= zWVE>gIvi6~pb~hM$Y9_X3P=B6gknis>b{U!Oa^W9uym-D zMot-Mfg@F@iloEU+#ADh71mVmD23s>?v;Vkx7$w35~cVt3i~a;gXthK0k0pR6;MI*!joW(}>gn8+voD}bK{f2H`X_#J84hS)89n1DDx zWf+*k$$%M5;P)i{ieWO}*I+82YdJCv`|)W4_Sf6Ba3sRt@trteeSO0=BzEc1(w+zF zJty#O9otW@;mXoZBGAuxwtV-r?Z6=liz120LzuJ^)7IC|-iEomcH*%0+t9oyny8KL zM9cd6)@?XE5gkTq7)RDGvxDgqlwzI zP^)=pAP`B+#|bzRC!<}m zwWv#ir6Zy?jELGWB5DKP7>e2uidsigihvOc$9B#*Q(QrHM5M;Hq6OY07MPc)ZAnD7 zwwioKw#0fE-)6;Z{t?*=0OVjF2ONw-a80 zJb`!Mof`6l24BQXj6j|U#jK-P0gvCs@gN$=%5%9gCKvt+g1=h}{4~iRf_Jt(2$TN& z9)-)rm$$61|G+j}uq#nJ$}TO5TH1m0E@bh3Cq4q!jUSzW780&C5lKYp!(yab?ZJVO z&ZGC9Pm*0g16_!@xCpJd7)SFGwvtCl^a{MVbHqrUBSz}fIEaxtH4dVEr^Z2y)Twch zSk|d=h@zyu%s-b}pUlw+Y9`f9rbujTj-}&-{j_gEMO${@3I?;}o3oKoWSn#J`;q|A^v9 z{HrkMZkfXvxvLl>=LM0LtSXvAN7@QYb$Dg09oe2szf8T1gi&1jp!PSB;j1*-FuvA0 zZy4WfWvKY>EhOEc{CCO%JVx*&C7eQ%0#}np*U*sH(va6<25#W28#(VLEaV+>9j}I4 zaVxIEZJcojZ;QjYoy>R~JDDu*!mn^Qp2fX*4)@_dA`R|W5j>!ce;OTu=ohKaCy9Za z!lx(jq)?wv5?N{}!wdK!BTy|R_&t7vAJcXVc^&%+eo7i&%UB>`J<2oZ^6%#)wwV72 zM1rvXA{iJpaHWBn26h-&%usJoSY_a`L|nhUgeqLB*9Bf1#qYG|*HS^5`3L-w w-!k?b&;JMhO#q+gn?{1L1^>m1lyw?M<@+hjR5&)Fl&T@T2_+$t`Cmo<2k{SJ$N&HU literal 0 HcmV?d00001 diff --git a/bin/main/com/plugin/sshjplugin/SSHJFileCopierPlugin.class b/bin/main/com/plugin/sshjplugin/SSHJFileCopierPlugin.class new file mode 100644 index 0000000000000000000000000000000000000000..66de106de38a511d7ad3be94b126cc68b21e1672 GIT binary patch literal 6464 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 literal 0 HcmV?d00001 diff --git a/bin/main/com/plugin/sshjplugin/SSHJNodeExecutorPlugin$ExtractFailure.class b/bin/main/com/plugin/sshjplugin/SSHJNodeExecutorPlugin$ExtractFailure.class new file mode 100644 index 0000000000000000000000000000000000000000..a8a822fd9020984a582712d905a4481a4c9a3f93 GIT binary patch literal 1193 zcmcIjZEF)j5Pl{}b4hwOZLF{LrE0+>4c-^ORRswtDxr#zfK-LeWlb-=+?KsN+yCJo zQLq&J;1BReiL;x-s_}~kIoO+-otbCm*_p53K7Rpl7aI<8ge_l9yn`?u1(6rWd*e*( z^&UOhQA61|l75;f{mgo7bdC}&{A61MVXCEr0%3V9-U%-h(a3u`7)w7Pl%&>5PvQ}w z);+!MB|3;kTZE#PB32RMVb`D!6BUX?2Uxa<31EXigLb4acg!{IE#I3vA)?_)aYF KKZF~&h0=FiMleAD literal 0 HcmV?d00001 diff --git a/bin/main/com/plugin/sshjplugin/SSHJNodeExecutorPlugin.class b/bin/main/com/plugin/sshjplugin/SSHJNodeExecutorPlugin.class new file mode 100644 index 0000000000000000000000000000000000000000..99ccf731e8a7549c2320045e7eb9dc439e6d605f GIT binary patch literal 12243 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 GR{uY74fy#0 literal 0 HcmV?d00001 diff --git a/bin/main/com/plugin/sshjplugin/model/SSHJAuthentication.class b/bin/main/com/plugin/sshjplugin/model/SSHJAuthentication.class new file mode 100644 index 0000000000000000000000000000000000000000..9fc0bb60cfb524461bfca9817e7b613a96387143 GIT binary patch literal 5110 zcmbtY`+F4S6@I^DlUX(cLgbP~zy(&=gv){zkOW1DgoK17nh+XOtCQVHGGVi`ot;I% zTD7$o+S;31d$Gk{)7l%Lgaj1r4O@G)+WY+<==1asXz6=qH@iu81M%q(+4*L^^PT&9 z&iV4%e=l4Da0~vap-f?O%pQrR5}83O8BM2$hI7jiJ8mYTU0uxw_GH{4GwE6}!?oOE~N3aKYzc&*x$B`BmMbP}bK%b9Pxt%e`HpH}Y0=nY-<^Jqo@?=0O1E zSgNBM%M{8ZwLSBo5Y(Y#zN~K0u>jXqVI^+Vu!`QDd(3r&uv(#N&~y*wDJl{DKkBmJh+ZS*I+RJ3D8 zqQ`J7dCz_HxkDBcW6L!qPuG~qp-;gMx!)LROPX#p9UB_4PMq*SDL`yYSj_1LiArGK zv6z_>)TA}stZ==D49kwTbmVsewBQyU3$cw+jMVnE1h4};HQcIj+yCVo9rd8n8bN0z zjf9sHEq>gl5SnUPaxj!h6QrRWEJH`k<5k#=JsR#%h=>YvL71dK%s$oiW<_kfWYdglE*=e{#Le#9I1qX=fQbY>tA|<#Nf0Ep2(-DAQL%YJw zrM)RI-kY%!anlLsp842l=u}uXvz+Cq{J2{o+GJP>Gahp7kYgJ05V?g|*g`pjLTMVt zZNm+vXj^?KfDUvEL=O=arOfCNio-hIj$W1yBb73fteKlikrr8;Q>;N)^yzqq_}TB; z*&-akL3E3A@6&O=?5vsQ+|GOfo)L?F9WjZ;vRTN*%chPh`2Aq$Hwh7Gk9<4 zkXJtWMM~!Yy;mR7@G(}e$!ebk;N7ebrW3&90>hK|q>fMF3D$g>4hp%-=8z?9C{CQb zLnnu+s31$GswRuGk;9(j$5U)fCDbcmP>Rsg_^gJ{OdAlhO6vF=KF?0?+Koeov&V4^ zrkS**#sI#6QyRX=&RG<%*%gzZrFA@uFA*ErsC6XGj)4J%9g)7q66lznSI$TcUs2dP zyABLOscIo8{+f=jV^m?jl$Fj*f5M89TA`&xq3FAc|7Q_F|4V`WrjF;Nz%G@FnG4XA zqUy)D8KTlHI7eL{5*HKyed=N zA{pO)s^c~MjCJa$k;q8p+)D&qbKbn_Wujpo`Tjy8Un#3!>G-wGyn%v4HT;GhsxYjy z+g+L1P?KdQ;`%hZQ*s#X-v>!lcVb&6jonz zdSW%1IX|&wOlL2*MGb#ZxTy%grE0#0zY!|eAXTXF8U#Zu)ds5>igI3@{ruy-fWMIY zi1#FJyeDTu@5!mfd-96pJ$XR_79Nl|^1Ym#s&PGk7xOKHGLP@ty7O34xA7cm>NcFi z^12NZSfOwZYh)FcRpboG%TUMP<(>jH@FB?1E$0nv1!{=xHCTlW+z(}0Vk0*34OlC8 zGzyfJC{@PQW<>ei!j+FZTSb}jAo)^t_3Sv_R`&uTW7xj=IlezhDmg9ZmTCfy zyC!gZIk;Xvfjbor*2!|?1ooFv-PTd8XupVqy%*8ZTYcAgbZs0%&lv7Gk9%Lvo_VK2 zyLV>O1PnP7AH~XgU;fYl4{e%usMR-)p?cqjF(fCz{x*RVyo~zB@W?pc_XLHVdK9bbwY>3X zr%;Q_JXt(&dJHd|VSKmaPVC1xhB>r7h;cQbGU^!L84yOvVMc)Y5JH$)u#VIb($}Jy zyZ19Ih8g{1jQ&H6{8RjI&?#)g1y+DJu@ish74KhYz`wCeRqUkcEH3w6hVS&@as1@ci-Fd_v!@uJ1tm%(&^$*PZA7EY0WB>pF literal 0 HcmV?d00001 diff --git a/bin/main/com/plugin/sshjplugin/model/SSHJBase.class b/bin/main/com/plugin/sshjplugin/model/SSHJBase.class new file mode 100644 index 0000000000000000000000000000000000000000..40431eaa793ba0e4b371c9c699e50c21fb1e2d24 GIT binary patch literal 1966 zcmb7_ZBNud5Xb)u2M48i$dk`n6!AEaDn4^QLLh1`XmX12Wv^_GVz0flg# z^t~U-IJ52D)moyyxZUk`=D)w0o$3Af`Q;mcn^-HNPhi&eTV~tcYdW48hC92-vgL0} z*R0o{K3}y$Swul#Y}eYiOxN<7=8MLzv?Bp6w%)W_QeeE+GpI*_<29=SdTdein@t%A zRBC+OcI3NOBQ%3OZ(G{$lH)`3rNW49$#=~5J&(?Dd~b8F)nI?|jJ5+A1qW;0ccH*B zChzSCEUI&oSaQSaS<^{FJAN2>DvCne4=A#UR4~uHND@8S4c>P=CweH*U!LC*D6CPI zH1uOi$8n4gL*RstVQ9QMrDFt|K&j?D{%NZ*(^jm4n;EeS8%=$ zLK$t!D9%@${mJrt3OUI?bIj-%z@Wf!a;Rt0v_wvSyZ=<8Snje89it={{wZgc|3{58 zSn5X6sgT74>7QzZffjTWF+@@XlrA8Ft1-?$DKX3l!8}b0ft9c6IL4V9A?41#z*PCq zJhOL^Ls7Le_1jf+s)rSC>nLH2Y?)QrPN&s6rb^C^3tZqrx#r2p4DFqkb9ktllpAx+ zb)*+n=eLTuPto^!Z#@Y7UcHyp0)(ar~BiE%JPA@|d_ zrbs`kK^l2Iq-N?8HB*nMnTkrylyNmvo@gE+1?nssC+KM%eY65fmCu;0^nb!M4<|p+ zRv%8&IF16prg4JWaFVXCgfq0G$8I=_bMz!|9v2cxlV+A2sC|D`CR8YTuGcC(oubM;9zCp6J~ocXlnN&CG3(?n2xQIsSwD*5g>e*46>A)PBRO#lnZhl%XuJX7G#+~ZX5x^MZ+LB=x;49 zmGr&d%7Wa^1-XN}c_6p4AS(n?JpzQQmO<`eCDFP`9l+PDeMR-{_=C@Q^gUB$n<*Nt KTV+p_)#@*b34`bW literal 0 HcmV?d00001 diff --git a/bin/main/com/plugin/sshjplugin/model/SSHJConnection$AuthenticationType.class b/bin/main/com/plugin/sshjplugin/model/SSHJConnection$AuthenticationType.class new file mode 100644 index 0000000000000000000000000000000000000000..6d0e0ce612bb4015eeacf14230c6a048a22178a3 GIT binary patch literal 1324 zcmbtTTTc@~7(K(5Zo4cOi-26bS8WSYG3rYz770eAK+Hmu#;55rmL=QWW-pljD}@J$ zM&h$S%J|K)!Wv^j(tX&OZ@$|(Gw1vH>+2bSXLzb&fZ?U-cFJ8lY+Fv*_xBItZO7dc zcG)oAzFT!2N0@=-I{B3_*cVPV23aaqw;Zt*c3Q&Q z;VoM*mj#8L7UfwV!uQ++`&B+cNxYK1bTiTIy&wn z&yeMw$9tyRr6(&TX>PX;==m$_3Fd!}h&qEB!QB4HU?kJx+bQ<>Lh?!qIv$|N@H|<6 z)h1C?5}+;`UJ{gQB>iZH8+xXAW65hD|69|GGMd;MR@>o0=uyP`L{V9x+9!o&f}pR{ z5P1=1;j4H;cYO7!Ag%m6d9@`{mqLTA)TX8K09r|Oz}SqG9C=c-Y8aw8ova4g@>~1{ z*31nnCm1}0So!!K(IQ<0?k?E61m}+h)rL$&LBHC1DPjP$U zGkMOToL6HP5VE9?hjEP(#mHV-j+IXF=m*&U^Awb#n)6tQ*2h?snUv2^#!_wiH@VJE ADF6Tf literal 0 HcmV?d00001 diff --git a/bin/main/com/plugin/sshjplugin/model/SSHJConnection.class b/bin/main/com/plugin/sshjplugin/model/SSHJConnection.class new file mode 100644 index 0000000000000000000000000000000000000000..efa49821e05131cfb8b03143c46cab31ce962719 GIT binary patch literal 1439 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 literal 0 HcmV?d00001 diff --git a/bin/main/com/plugin/sshjplugin/model/SSHJExec.class b/bin/main/com/plugin/sshjplugin/model/SSHJExec.class new file mode 100644 index 0000000000000000000000000000000000000000..373c44df8f72481808dfdca476b4b3ca9d0274ad GIT binary patch literal 2363 zcmeHI-A)uS6h2*6mL2&M1^=Ze;G(i)BHjogQG|e5)MSyw%Q91Ui!<${?QDP-@C|$d z-@!x^z4xJvr?afAVU&$}W8%fkbUI(ZIp_O&PQQHp@Cg7G;BE;D1f~NO`$?2Gg!GMR z?j8nV@mynSBX&Ucvy~PmdBvAp2FkRu^4=0PZDKP!nS}#^$MkAQf^USfDYBabeFki{m zhE_#v$M`yxArD?3+G_l6@S;n%pM~M7Y+*cydVJS>ZhHVIfxQzGE^jqCjk#^Akha+> z=-C`quc+NmIPGj58mGom)#6%H~|*D)AW zJqrT)l(x>7YJ2lmkM?oZh4C;+xD1`(G+zw}fjfuuAX$9+^|R`j|(& z!7P84+9btl(457&beW2>CA5-GF_QzLR-uqiL$BN;??18RMCZD*aNyo7`r}ouV_{27 zEtd0Sr6#%cP0)$dehI1sMl)zvaS|BZ6vp5= zw}sVcU<$CC;ZfTM1vmx$I4VF12C`=vJp6h(8xP_){4)&WeFSF~vK0W{M;LuMHufIQ zea>*_@$QfSSKtEP2eYvU7vU1V5x9)=y)w{ThO2Nbmtis|hM8Q3i7e|*7B1Fy7T0qa vin$EaFq0d@Y!1V{T!sqFbuk>pkf-Fk9ELu)g>e_)Hr&A{fR9%Z8Rx$Nc$TC& literal 0 HcmV?d00001 diff --git a/bin/main/com/plugin/sshjplugin/model/SSHJScp.class b/bin/main/com/plugin/sshjplugin/model/SSHJScp.class new file mode 100644 index 0000000000000000000000000000000000000000..65e9b0e2fb1c806dcbf2be3da9d128e73c69c929 GIT binary patch literal 3269 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 nfGnNLQ>tJYcd$!2Groef01 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 pZ(*g#`zfdlOzY>K?W}R<;<2T9wB7_z$ zc^y|M_`8Ca8HU#oc#X4F$U}w`2R1<`v1?>1k&u5p#fAO>_?QjQ8BsMJz#6xxg*<Xy$MI{jl){j<$s30oHR}G@5{aBJQEF`<>W~1cv^s=Yz zY4yJS74{~!<(&4;i~gM6+xD5=5CTc8?L}{9XJ_Vp=6#;`opOJL8GX~~&Fc1b$(#!^NKbWGVvBeP&Qrur`r z2Ha&cFK~A1U%C|+(CMh*O5at1ANoCvlxt^nzx^qT8|IBt$3D5ooEh6gb&mySgsG zQ5@HBY)jVq;5zznLZF$jqeWLT?*jdiN25yIQ#$$)5eOQolrOwMcbyF%jr!zH>ljev zjwu%%^C=VYjw~vgxn;-5%POLZ(V&i)V$|JWxmt5p$9oEL|CTjXKpM^q9QG75?U-V4 z-EoZKY{AVHTr!kKHiUBsDzNu;e1J<0z-}0>ArPrUudFBGC#(u6V-m*|4Z{NA>LgY* zBOmiO&g&S#Re?~_&K3P3fx+?)+u-~#a=RMn&X_%z#X0qKaSgx6s<7-KoQ%9v4QvyBrELE9$UAs5u$eiKG z-n>^0dzZX&7{UZ*)G3-3h}IFWDm~|y9s8LAi$@kTd??Vl5w?lh%BBKZ(yu}S7yi>h zV**<|_yPls&1=szOQ+9kqjZimw%%UX>eQwy*n8A`a7jSdSoCaPwlC9o~e486vwb(|yEh06goJ5<8OaNs9g zexRU+!hsU5`5VFAaH;-xKd0wzups}G+ciZz@@ zk(OWJ48CUnTi%?{iSmLdO284yNs(|D34T>?9{1>3GrlC|eM;Ve&+!0@^l^dvKg1&n z`+~NQ@eyY~rp*!Te1icEZ5lem(ePh1Z_@CI1_MGPyC9PEt%<&=5I*r@`v*}1{C~WT zG!->#!qe)7=Vz=m;XXxVSiF&FWrX4IMusXQRNWgH^G60=5> Mock(Resource) { + getContents() >> Mock(ResourceMeta) { + writeContent(_) >> { args -> + args[0].write('test.'.bytes) + 7L + } + } + } + + getResource('keys/node.key') >> Mock(Resource) { + getContents() >> Mock(ResourceMeta) { + writeContent(_) >> { args -> + args[0].write('-----BEGIN OPENSSH PRIVATE KEY-----'.bytes) + 7L + } + } + } + } + + def framework = Mock(Framework) { + getFrameworkProjectMgr() >> Mock(ProjectManager) { + getFrameworkProject(_) >> rundeckFramework + } + getPropertyLookup() >> PropertyLookup.create(properties) + getProjectManager() >> Mock(ProjectManager) { + getFrameworkProject(_) >> rundeckFramework + } + } + + ExecutionContextImpl.builder() + .framework(framework) + .executionListener(logger) + .storageTree(storage) + .dataContext(new BaseDataContext(dataContext)) + .frameworkProject("test") + .build() + } + + + def "authenticate using node password"(){ + + given: + + String[] command = ["ls -lrt"] + + def logger = Mock(ExecutionListener) { + createOverride() >> Mock(ExecutionListenerOverride) + } + + def rundeckFramework = Mock(IRundeckProject) + def properties = new Properties() + properties.setProperty("fwkprop","fwkvalue") + def context = getContext(properties, rundeckFramework, logger) + + SSHClient client = Mock(SSHClient){ + getTransport()>>Mock(Transport){ + getConfig()>>SSHJDefaultConfig.init().getConfig() + } + } + + def plugin = new SSHJNodeExecutorPlugin() + plugin.sshClient = client + + def node = new NodeEntryImpl("test") + node.setAttributes(["username":"test", + "osFamily":"linux", + "hostname":"localhost", + "ssh-connect-timeout":"3", + "ssh-command-timeout":"3", + "ssh-authentication":"password", + "ssh-password-storage-path":"keys/password", + "sudo-password-storage-path":"keys/password", + "sudo-command-enabled":"true" + ]) + + when: + def result = plugin.executeCommand(context, command, node) + + then: + 1 * client.connect(_) + 1 * client.isConnected() >> true + 1 * client.startSession()>>Mock(Session){ + exec(_)>>Mock(Session.Command){ + getExitStatus()>>0 + getInputStream()>>Mock(InputStream){ + read(_)>>-1 + } + getErrorStream()>>Mock(InputStream){ + read(_)>>-1 + } + } + } + 1 * logger.log(3, "Authenticating using password: keys/password") + result!=null + result.success + + } + + def "authenticate using node key"(){ + + given: + + String[] command = ["ls -lrt"] + + def logger = Mock(ExecutionListener) { + createOverride() >> Mock(ExecutionListenerOverride) + } + + def rundeckFramework = Mock(IRundeckProject) + def properties = new Properties() + properties.setProperty("fwkprop","fwkvalue") + def context = getContext(properties,rundeckFramework, logger) + + SSHClient client = Mock(SSHClient){ + getTransport()>>Mock(Transport){ + getConfig()>>SSHJDefaultConfig.init().getConfig() + } + } + + def plugin = new SSHJNodeExecutorPlugin() + plugin.sshClient = client + + def node = new NodeEntryImpl("test") + node.setAttributes(["username":"test", + "osFamily":"linux", + "hostname":"localhost", + "ssh-connect-timeout":"3", + "ssh-command-timeout":"3", + "ssh-authentication":"privateKey", + "ssh-key-storage-path":"keys/node.key", + "sudo-command-enabled":"true" + ]) + + when: + def result = plugin.executeCommand(context, command, node) + + then: + 1 * client.connect(_) + 1 * client.isConnected() >> true + 1 * client.startSession()>>Mock(Session){ + exec(_)>>Mock(Session.Command){ + getExitStatus()>>0 + getInputStream()>>Mock(InputStream){ + read(_)>>-1 + } + getErrorStream()>>Mock(InputStream){ + read(_)>>-1 + } + } + } + 1 * logger.log(3, "Authenticating using private key") + result!=null + result.success + + } + + + def "authenticate using password project level"(){ + + given: + + String[] command = ["ls -lrt"] + + def logger = Mock(ExecutionListener) { + createOverride() >> Mock(ExecutionListenerOverride) + } + + def rundeckFramework = Mock(IRundeckProject) { + hasProperty('project.ssh-authentication') >> true + getProperty('project.ssh-authentication') >> "password" + hasProperty('project.ssh-password-storage-path') >> true + getProperty('project.ssh-password-storage-path')>> "keys/password" + } + + def properties = new Properties() + properties.setProperty("fwkprop","fwkvalue") + def context = getContext(properties,rundeckFramework, logger) + + SSHClient client = Mock(SSHClient){ + getTransport()>>Mock(Transport){ + getConfig()>>SSHJDefaultConfig.init().getConfig() + } + } + + def plugin = new SSHJNodeExecutorPlugin() + plugin.sshClient = client + + def node = new NodeEntryImpl("test") + node.setAttributes(["username":"test", + "osFamily":"linux", + "hostname":"localhost" + ]) + + when: + def result = plugin.executeCommand(context, command, node) + + then: + 1 * client.connect(_) + 1 * client.isConnected() >> true + 1 * client.startSession()>>Mock(Session){ + exec(_)>>Mock(Session.Command){ + getExitStatus()>>0 + getInputStream()>>Mock(InputStream){ + read(_)>>-1 + } + getErrorStream()>>Mock(InputStream){ + read(_)>>-1 + } + } + } + 1 * logger.log(3, "Authenticating using password: keys/password") + result!=null + result.success + + } + + def "authenticate using key project level"(){ + + given: + + String[] command = ["ls -lrt"] + + def logger = Mock(ExecutionListener) { + createOverride() >> Mock(ExecutionListenerOverride) + } + + def rundeckFramework = Mock(IRundeckProject) { + hasProperty('project.ssh-authentication') >> true + getProperty('project.ssh-authentication') >> "privateKey" + hasProperty('project.ssh-key-storage-path') >> true + getProperty('project.ssh-key-storage-path')>> "keys/node.key" + } + def properties = new Properties() + properties.setProperty("fwkprop","fwkvalue") + def context = getContext(properties,rundeckFramework, logger) + + SSHClient client = Mock(SSHClient){ + getTransport()>>Mock(Transport){ + getConfig()>>SSHJDefaultConfig.init().getConfig() + } + } + + def plugin = new SSHJNodeExecutorPlugin() + plugin.sshClient = client + + def node = new NodeEntryImpl("test") + node.setAttributes(["username":"test", + "osFamily":"linux", + "hostname":"localhost" + ]) + + when: + def result = plugin.executeCommand(context, command, node) + + then: + 1 * client.connect(_) + 1 * client.isConnected() >> true + 1 * client.startSession()>>Mock(Session){ + exec(_)>>Mock(Session.Command){ + getExitStatus()>>0 + getInputStream()>>Mock(InputStream){ + read(_)>>-1 + } + getErrorStream()>>Mock(InputStream){ + read(_)>>-1 + } + } + } + 1 * logger.log(3, "Authenticating using private key") + result!=null + result.success + + + + } + + + def "error getting key"(){ + + given: + + String[] command = ["ls -lrt"] + + def logger = Mock(ExecutionListener) { + createOverride() >> Mock(ExecutionListenerOverride) + } + + def rundeckFramework = Mock(IRundeckProject) + def properties = new Properties() + properties.setProperty("fwkprop","fwkvalue") + + def dataContext = [ + config: ["RD_TEST": "Value"] + ] + + def storage = Mock(StorageTree) { + getResource('keys/password') >> {throw new Exception("Cannot get password")} + getResource('keys/node.key') >> {throw new Exception("Cannot get key")} + } + + def framework = Mock(Framework) { + getFrameworkProjectMgr() >> Mock(ProjectManager) { + getFrameworkProject(_) >> rundeckFramework + } + getPropertyLookup() >> PropertyLookup.create(properties) + getProjectManager() >> Mock(ProjectManager) { + getFrameworkProject(_) >> rundeckFramework + } + } + + def context = ExecutionContextImpl.builder() + .framework(framework) + .executionListener(logger) + .storageTree(storage) + .dataContext(new BaseDataContext(dataContext)) + .frameworkProject("test") + .build() + + SSHClient client = Mock(SSHClient){ + getTransport()>>Mock(Transport){ + getConfig()>>SSHJDefaultConfig.init().getConfig() + } + } + + def plugin = new SSHJNodeExecutorPlugin() + plugin.sshClient = client + + def node = new NodeEntryImpl("test") + node.setAttributes(["username":"test", + "osFamily":"linux", + "hostname":"localhost", + "ssh-connect-timeout":"3", + "ssh-command-timeout":"3", + "ssh-authentication":"privateKey", + "ssh-key-storage-path":"keys/node.key", + "sudo-command-enabled":"true" + ]) + + when: + def result = plugin.executeCommand(context, command, node) + + then: + !result.success + result.failureMessage=="Failed to read SSH Key Storage stored at path: keys/node.key" + + + } + + + def "error getting password"(){ + + given: + + String[] command = ["ls -lrt"] + + def logger = Mock(ExecutionListener) { + createOverride() >> Mock(ExecutionListenerOverride) + } + + def rundeckFramework = Mock(IRundeckProject) + def properties = new Properties() + properties.setProperty("fwkprop","fwkvalue") + + def dataContext = [ + config: ["RD_TEST": "Value"] + ] + + def storage = Mock(StorageTree) { + getResource('keys/password') >> {throw new Exception("Cannot get password")} + getResource('keys/node.key') >> {throw new Exception("Cannot get key")} + } + + def framework = Mock(Framework) { + getFrameworkProjectMgr() >> Mock(ProjectManager) { + getFrameworkProject(_) >> rundeckFramework + } + getPropertyLookup() >> PropertyLookup.create(properties) + getProjectManager() >> Mock(ProjectManager) { + getFrameworkProject(_) >> rundeckFramework + } + } + + def context = ExecutionContextImpl.builder() + .framework(framework) + .executionListener(logger) + .storageTree(storage) + .dataContext(new BaseDataContext(dataContext)) + .frameworkProject("test") + .build() + + SSHClient client = Mock(SSHClient){ + getTransport()>>Mock(Transport){ + getConfig()>>SSHJDefaultConfig.init().getConfig() + } + } + + def plugin = new SSHJNodeExecutorPlugin() + plugin.sshClient = client + + def node = new NodeEntryImpl("test") + node.setAttributes(["username":"test", + "osFamily":"linux", + "hostname":"localhost", + "ssh-connect-timeout":"3", + "ssh-command-timeout":"3", + "ssh-authentication":"password", + "ssh-password-storage-path":"keys/password", + "sudo-command-enabled":"true" + ]) + + when: + def result = plugin.executeCommand(context, command, node) + + then: + !result.success + result.failureMessage=="Failed to read SSH Password stored at path: keys/password" + + + } + + def "error getting key passphrase"(){ + + given: + + String[] command = ["ls -lrt"] + + def logger = Mock(ExecutionListener) { + createOverride() >> Mock(ExecutionListenerOverride) + } + + def rundeckFramework = Mock(IRundeckProject) + def properties = new Properties() + properties.setProperty("fwkprop","fwkvalue") + + def dataContext = [ + config: ["RD_TEST": "Value"] + ] + + def storage = Mock(StorageTree) { + getResource('keys/password') >> {throw new Exception("Cannot get password")} + getResource('keys/node.key') >> Mock(Resource) { + getContents() >> Mock(ResourceMeta) { + writeContent(_) >> { args -> + args[0].write('test.'.bytes) + 7L + } + } + } + } + + def framework = Mock(Framework) { + getFrameworkProjectMgr() >> Mock(ProjectManager) { + getFrameworkProject(_) >> rundeckFramework + } + getPropertyLookup() >> PropertyLookup.create(properties) + getProjectManager() >> Mock(ProjectManager) { + getFrameworkProject(_) >> rundeckFramework + } + } + + def context = ExecutionContextImpl.builder() + .framework(framework) + .executionListener(logger) + .storageTree(storage) + .dataContext(new BaseDataContext(dataContext)) + .frameworkProject("test") + .build() + + SSHClient client = Mock(SSHClient){ + getTransport()>>Mock(Transport){ + getConfig()>>SSHJDefaultConfig.init().getConfig() + } + } + + def plugin = new SSHJNodeExecutorPlugin() + plugin.sshClient = client + + def node = new NodeEntryImpl("test") + node.setAttributes(["username":"test", + "osFamily":"linux", + "hostname":"localhost", + "ssh-connect-timeout":"3", + "ssh-command-timeout":"3", + "ssh-authentication":"privateKey", + "ssh-key-storage-path":"keys/node.key", + "ssh-key-passphrase-storage-path":"keys/password", + "sudo-command-enabled":"true" + ]) + + when: + def result = plugin.executeCommand(context, command, node) + + then: + !result.success + result.failureMessage=="Failed to read SSH Passphrase stored at path: keys/password" + + + } + +} From 8ed6901772666f5adb206bc7c9a4d4d0e1097969 Mon Sep 17 00:00:00 2001 From: Jesus Osuna Date: Tue, 29 Aug 2023 15:48:14 -0400 Subject: [PATCH 04/10] fix filesystem key storage support --- .../sshjplugin/model/SSHJAuthentication.java | 55 +++++++++++-------- .../sshjplugin/model/SSHJConnection.java | 2 + .../model/SSHJConnectionParameters.java | 5 +- 3 files changed, 38 insertions(+), 24 deletions(-) diff --git a/src/main/java/com/plugin/sshjplugin/model/SSHJAuthentication.java b/src/main/java/com/plugin/sshjplugin/model/SSHJAuthentication.java index 986ecea..dececa1 100644 --- a/src/main/java/com/plugin/sshjplugin/model/SSHJAuthentication.java +++ b/src/main/java/com/plugin/sshjplugin/model/SSHJAuthentication.java @@ -34,31 +34,42 @@ void authenticate(final SSHClient ssh) throws IOException { switch (authenticationType) { case privateKey: logger.log(3, "Authenticating using private key"); + String privateKeyStoragePath = connectionParameters.getPrivateKeyStoragePath(); + String privateKeyFilePath = connectionParameters.getPrivateKeyFilePath(); + String passphrasePath = connectionParameters.getPrivateKeyPassphraseStoragePath(); + FileKeyProvider keys = null; - String privateKeyPath = connectionParameters.getPrivateKeyStoragePath(); - try{ - privateKeyContent = connectionParameters.getPrivateKeyStorage(privateKeyPath); - } catch (Exception e) { - throw new SSHJBuilder.BuilderException("Failed to read SSH Key Storage stored at path: " + privateKeyPath); + if(passphrasePath!=null){ + try{ + passphrase = connectionParameters.getPrivateKeyPassphrase(passphrasePath); + } catch (Exception e) { + throw new SSHJBuilder.BuilderException("Failed to read SSH Passphrase stored at path: " + passphrasePath); + } } - - String passphrasePath = connectionParameters.getPrivateKeyPassphraseStoragePath(); - try{ - passphrase = connectionParameters.getPrivateKeyPassphrase(passphrasePath); - } catch (Exception e) { - throw new SSHJBuilder.BuilderException("Failed to read SSH Passphrase stored at path: " + passphrasePath); + if(privateKeyStoragePath!=null){ + logger.log(3, "[sshj-debug] Using SSH Storage key: " + privateKeyStoragePath); + try{ + privateKeyContent = connectionParameters.getPrivateKeyStorage(privateKeyStoragePath); + } catch (Exception e) { + throw new SSHJBuilder.BuilderException("Failed to read SSH Key Storage stored at path: " + privateKeyStoragePath); + } + KeyFormat format = KeyProviderUtil.detectKeyFileFormat(privateKeyContent,true); + keys = Factory.Named.Util.create(ssh.getTransport().getConfig().getFileKeyProviderFactories(), format.toString()); + if (passphrase == null) { + keys.init(new StringReader(privateKeyContent), null); + } else { + logger.log(3, "[sshj-debug] Using Passphrase: " + passphrasePath); + keys.init(new StringReader(privateKeyContent), PasswordUtils.createOneOff(passphrase.toCharArray())); + } } - - KeyFormat format = KeyProviderUtil.detectKeyFileFormat(privateKeyContent,true); - FileKeyProvider keys = Factory.Named.Util.create(ssh.getTransport().getConfig().getFileKeyProviderFactories(), format.toString()); - - logger.log(3, "[sshj-debug] Using ssh keyfile: " + privateKeyPath); - - if (passphrase == null) { - keys.init(new StringReader(privateKeyContent), null); - } else { - logger.log(3, "[sshj-debug] Using Passphrase: " + passphrasePath); - keys.init(new StringReader(privateKeyContent), PasswordUtils.createOneOff(passphrase.toCharArray())); + if(privateKeyFilePath!=null){ + logger.log(3, "[sshj-debug] Using SSH Keyfile: " + privateKeyFilePath); + if (passphrase == null) { + keys = (FileKeyProvider) ssh.loadKeys(privateKeyFilePath); + } else { + keys = (FileKeyProvider) ssh.loadKeys(privateKeyFilePath, passphrase); + logger.log(3, "[sshj-debug] Using Passphrase: " + passphrasePath); + } } ssh.authPublickey(username, keys); break; diff --git a/src/main/java/com/plugin/sshjplugin/model/SSHJConnection.java b/src/main/java/com/plugin/sshjplugin/model/SSHJConnection.java index 95935bf..54d4cd1 100644 --- a/src/main/java/com/plugin/sshjplugin/model/SSHJConnection.java +++ b/src/main/java/com/plugin/sshjplugin/model/SSHJConnection.java @@ -25,6 +25,8 @@ static enum AuthenticationType { String getPasswordStoragePath(); + String getPrivateKeyFilePath(); + String getPassword(String path) throws IOException; String getSudoPasswordStoragePath(); diff --git a/src/main/java/com/plugin/sshjplugin/model/SSHJConnectionParameters.java b/src/main/java/com/plugin/sshjplugin/model/SSHJConnectionParameters.java index ba8ff13..17f9114 100644 --- a/src/main/java/com/plugin/sshjplugin/model/SSHJConnectionParameters.java +++ b/src/main/java/com/plugin/sshjplugin/model/SSHJConnectionParameters.java @@ -59,7 +59,7 @@ public String getPrivateKeyPath() throws IOException { }else{ - privateKeyFile = getPrivateKeyfilePath(); + privateKeyFile = getPrivateKeyFilePath(); context.getExecutionListener().log(3, "[sshj-debug] Using ssh keyfile: " + privateKeyFile); } @@ -96,7 +96,8 @@ public String getPrivateKeyStorage(String path) throws IOException { return propertyResolver.getPrivateKeyStorage(path); } - String getPrivateKeyfilePath() { + @Override + public String getPrivateKeyFilePath() { String path = propertyResolver.resolve(SSHJNodeExecutorPlugin.NODE_ATTR_SSH_KEYPATH); if (path == null && framework.hasProperty(Constants.SSH_KEYPATH_PROP)) { //return default framework level From ce2d58f9af4cd633ee8cb475f59eeefb1ecc1d4b Mon Sep 17 00:00:00 2001 From: Jesus Osuna Date: Tue, 29 Aug 2023 16:07:42 -0400 Subject: [PATCH 05/10] rename privateKeyFilePath variable --- .../plugin/sshjplugin/model/SSHJAuthentication.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/plugin/sshjplugin/model/SSHJAuthentication.java b/src/main/java/com/plugin/sshjplugin/model/SSHJAuthentication.java index dececa1..0ed89af 100644 --- a/src/main/java/com/plugin/sshjplugin/model/SSHJAuthentication.java +++ b/src/main/java/com/plugin/sshjplugin/model/SSHJAuthentication.java @@ -35,7 +35,7 @@ void authenticate(final SSHClient ssh) throws IOException { case privateKey: logger.log(3, "Authenticating using private key"); String privateKeyStoragePath = connectionParameters.getPrivateKeyStoragePath(); - String privateKeyFilePath = connectionParameters.getPrivateKeyFilePath(); + String privateKeyFileSystemPath = connectionParameters.getPrivateKeyFilePath(); String passphrasePath = connectionParameters.getPrivateKeyPassphraseStoragePath(); FileKeyProvider keys = null; @@ -62,12 +62,12 @@ void authenticate(final SSHClient ssh) throws IOException { keys.init(new StringReader(privateKeyContent), PasswordUtils.createOneOff(passphrase.toCharArray())); } } - if(privateKeyFilePath!=null){ - logger.log(3, "[sshj-debug] Using SSH Keyfile: " + privateKeyFilePath); + if(privateKeyFileSystemPath!=null){ + logger.log(3, "[sshj-debug] Using SSH Keyfile: " + privateKeyFileSystemPath); if (passphrase == null) { - keys = (FileKeyProvider) ssh.loadKeys(privateKeyFilePath); + keys = (FileKeyProvider) ssh.loadKeys(privateKeyFileSystemPath); } else { - keys = (FileKeyProvider) ssh.loadKeys(privateKeyFilePath, passphrase); + keys = (FileKeyProvider) ssh.loadKeys(privateKeyFileSystemPath, passphrase); logger.log(3, "[sshj-debug] Using Passphrase: " + passphrasePath); } } From cc3d2abdff99de8c9c3e72aba45b5db12cf2618c Mon Sep 17 00:00:00 2001 From: Jesus Osuna Date: Wed, 30 Aug 2023 10:11:27 -0400 Subject: [PATCH 06/10] keep authentication order --- .../sshjplugin/model/SSHJAuthentication.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/plugin/sshjplugin/model/SSHJAuthentication.java b/src/main/java/com/plugin/sshjplugin/model/SSHJAuthentication.java index 0ed89af..50d3c5a 100644 --- a/src/main/java/com/plugin/sshjplugin/model/SSHJAuthentication.java +++ b/src/main/java/com/plugin/sshjplugin/model/SSHJAuthentication.java @@ -46,6 +46,15 @@ void authenticate(final SSHClient ssh) throws IOException { throw new SSHJBuilder.BuilderException("Failed to read SSH Passphrase stored at path: " + passphrasePath); } } + if(privateKeyFileSystemPath!=null){ + logger.log(3, "[sshj-debug] Using SSH Keyfile: " + privateKeyFileSystemPath); + if (passphrase == null) { + keys = (FileKeyProvider) ssh.loadKeys(privateKeyFileSystemPath); + } else { + keys = (FileKeyProvider) ssh.loadKeys(privateKeyFileSystemPath, passphrase); + logger.log(3, "[sshj-debug] Using Passphrase: " + passphrasePath); + } + } if(privateKeyStoragePath!=null){ logger.log(3, "[sshj-debug] Using SSH Storage key: " + privateKeyStoragePath); try{ @@ -62,15 +71,6 @@ void authenticate(final SSHClient ssh) throws IOException { keys.init(new StringReader(privateKeyContent), PasswordUtils.createOneOff(passphrase.toCharArray())); } } - if(privateKeyFileSystemPath!=null){ - logger.log(3, "[sshj-debug] Using SSH Keyfile: " + privateKeyFileSystemPath); - if (passphrase == null) { - keys = (FileKeyProvider) ssh.loadKeys(privateKeyFileSystemPath); - } else { - keys = (FileKeyProvider) ssh.loadKeys(privateKeyFileSystemPath, passphrase); - logger.log(3, "[sshj-debug] Using Passphrase: " + passphrasePath); - } - } ssh.authPublickey(username, keys); break; case password: From 37ecbc69a810846e3893b6c7ba2b9f08d8ac2191 Mon Sep 17 00:00:00 2001 From: Jesus Osuna Date: Tue, 5 Sep 2023 14:04:26 -0300 Subject: [PATCH 07/10] fix condition --- .../sshjplugin/model/SSHJAuthentication.java | 8 +- .../model/SSHJAuthenticationTest.groovy | 114 ++++++++++++++++++ 2 files changed, 118 insertions(+), 4 deletions(-) create mode 100644 src/test/groovy/com/plugin/sshjplugin/model/SSHJAuthenticationTest.groovy diff --git a/src/main/java/com/plugin/sshjplugin/model/SSHJAuthentication.java b/src/main/java/com/plugin/sshjplugin/model/SSHJAuthentication.java index 50d3c5a..01df715 100644 --- a/src/main/java/com/plugin/sshjplugin/model/SSHJAuthentication.java +++ b/src/main/java/com/plugin/sshjplugin/model/SSHJAuthentication.java @@ -39,14 +39,14 @@ void authenticate(final SSHClient ssh) throws IOException { String passphrasePath = connectionParameters.getPrivateKeyPassphraseStoragePath(); FileKeyProvider keys = null; - if(passphrasePath!=null){ + if(passphrasePath != null){ try{ passphrase = connectionParameters.getPrivateKeyPassphrase(passphrasePath); } catch (Exception e) { throw new SSHJBuilder.BuilderException("Failed to read SSH Passphrase stored at path: " + passphrasePath); } } - if(privateKeyFileSystemPath!=null){ + if(privateKeyFileSystemPath != null && privateKeyStoragePath == null){ logger.log(3, "[sshj-debug] Using SSH Keyfile: " + privateKeyFileSystemPath); if (passphrase == null) { keys = (FileKeyProvider) ssh.loadKeys(privateKeyFileSystemPath); @@ -55,7 +55,7 @@ void authenticate(final SSHClient ssh) throws IOException { logger.log(3, "[sshj-debug] Using Passphrase: " + passphrasePath); } } - if(privateKeyStoragePath!=null){ + if(privateKeyStoragePath != null){ logger.log(3, "[sshj-debug] Using SSH Storage key: " + privateKeyStoragePath); try{ privateKeyContent = connectionParameters.getPrivateKeyStorage(privateKeyStoragePath); @@ -75,7 +75,7 @@ void authenticate(final SSHClient ssh) throws IOException { break; case password: String passwordPath = connectionParameters.getPasswordStoragePath(); - if(passwordPath!=null){ + if(passwordPath != null){ logger.log(3, "Authenticating using password: " + passwordPath); } try{ diff --git a/src/test/groovy/com/plugin/sshjplugin/model/SSHJAuthenticationTest.groovy b/src/test/groovy/com/plugin/sshjplugin/model/SSHJAuthenticationTest.groovy new file mode 100644 index 0000000..17e9f94 --- /dev/null +++ b/src/test/groovy/com/plugin/sshjplugin/model/SSHJAuthenticationTest.groovy @@ -0,0 +1,114 @@ +package com.plugin.sshjplugin.model + +import com.dtolabs.rundeck.plugins.PluginLogger +import com.hierynomus.sshj.userauth.keyprovider.OpenSSHKeyV1KeyFile +import net.schmizz.sshj.Config +import net.schmizz.sshj.SSHClient +import net.schmizz.sshj.common.Factory +import net.schmizz.sshj.transport.Transport +import net.schmizz.sshj.userauth.keyprovider.FileKeyProvider +import spock.lang.Specification +import net.schmizz.sshj.userauth.keyprovider.PKCS8KeyFile; +import net.schmizz.sshj.userauth.keyprovider.OpenSSHKeyFile; +import net.schmizz.sshj.userauth.keyprovider.PuTTYKeyFile + + +class SSHJAuthenticationTest extends Specification { + + def "authenticate with private key on filesystem no passphrase"() { + given: + SSHClient sshClient = Mock(SSHClient) + PluginLogger pluginLogger = Mock(PluginLogger) + SSHJConnection connectionParameters = Mock(SSHJConnection){ + 1 * getAuthenticationType() >> SSHJConnection.AuthenticationType.privateKey + 1 * getPrivateKeyFilePath() >> "keys/rundeck/storage" + 0 * getPrivateKeyPassphrase(_) + 0 * getPrivateKeyStorage(_) + } + + SSHJAuthentication auth = new SSHJAuthentication(connectionParameters,pluginLogger) + + when: + auth.authenticate(sshClient) + + then: + 1 * sshClient.authPublickey(_, _) + 1 * sshClient.loadKeys(_) + 0 * sshClient.loadKeys(_, _) + } + + def "authenticate with private key on filesystem with passphrase"() { + given: + String passphraseStoragePath = "keys/rundeck/storage/passphrase" + + SSHClient sshClient = Mock(SSHClient) + PluginLogger pluginLogger = Mock(PluginLogger) + SSHJConnection connectionParameters = Mock(SSHJConnection){ + 1 * getAuthenticationType() >> SSHJConnection.AuthenticationType.privateKey + 1 * getPrivateKeyFilePath() >> "keys/rundeck/storage" + 1 * getPrivateKeyPassphraseStoragePath() >> passphraseStoragePath + 1 * getPrivateKeyPassphrase(passphraseStoragePath) >> "pass" + 0 * getPrivateKeyStorage(_) + } + SSHJAuthentication auth = new SSHJAuthentication(connectionParameters,pluginLogger) + + when: + auth.authenticate(sshClient) + + then: + 0 * sshClient.loadKeys(_) + 1 * sshClient.authPublickey(_, _) + } + + + + /* def "authenticate with private key Rundeck storage no passphrase"() { + given: + String keyStoragePath = "keys/rundeck/storage" + SSHClient sshClient = Mock(SSHClient){ + getTransport() >> Mock(Transport){ + getConfig() >> Mock(Config){ + getFileKeyProviderFactories() >> providerFactoriesList() + } + } + } + PluginLogger pluginLogger = Mock(PluginLogger) + SSHJConnection connectionParameters = Mock(SSHJConnection){ + 1 * getAuthenticationType() >> SSHJConnection.AuthenticationType.privateKey + 1 * getPrivateKeyStoragePath() >> keyStoragePath + 1 * getPrivateKeyStorage(keyStoragePath) >> "-----BEGIN OPENSSH PRIVATE KEY-----\n" + + "b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABCBHpyIOm\n" + + "Y45NDeuHxAzGCUAAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIKUYWSH2YrYUH3IA\n" + + "t40IcM0ykM03oFRI7m+5jEK+fE4LAAAAoBIhOVxejwLYvIKIBgNQOe0j8h2nnz/+sEYUDc\n" + + "ug6KrlPxQ7kuL67It/Tb7IxAGzVWT3g3fkQMGNU/8uxRHAf5fQC9aYValFPr21g7I39OqR\n" + + "MbPXHnD8a+DwAw3ArakcZigzWqncuX5cuBgpr5+x/iXWAz0lAHJH1d5HaIsoy1K6VmMR+b\n" + + "GN7ixrjWwMVBM+Lv8DdRN5UnniX5grj6M8P0A=\n" + + "-----END OPENSSH PRIVATE KEY-----" + 0 * getPrivateKeyPassphrase(_) + 0 * getPrivateKeyStorage(_) + } + + SSHJAuthentication auth = new SSHJAuthentication(connectionParameters,pluginLogger) + OpenSSHKeyV1KeyFile + + when: + auth.authenticate(sshClient) + + then: + true + }*/ + + + public static List > providerFactoriesList() { + List> factories = new ArrayList<>(); + factories.add(OpenSSHKeyV1KeyFile); + factories.add(PKCS8KeyFile); + factories.add(OpenSSHKeyFile); + factories.add(PuTTYKeyFile); + + return factories; + } + + + } + From ce0c4d025a82aef8a1a2fcca6c88bc4aa8540923 Mon Sep 17 00:00:00 2001 From: Jesus Osuna Date: Wed, 6 Sep 2023 14:40:34 -0300 Subject: [PATCH 08/10] avoid npe on keys --- .../sshjplugin/model/SSHJAuthentication.java | 14 +++++---- .../model/SSHJAuthenticationTest.groovy | 29 ++++++++++--------- 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/src/main/java/com/plugin/sshjplugin/model/SSHJAuthentication.java b/src/main/java/com/plugin/sshjplugin/model/SSHJAuthentication.java index 01df715..967c1bc 100644 --- a/src/main/java/com/plugin/sshjplugin/model/SSHJAuthentication.java +++ b/src/main/java/com/plugin/sshjplugin/model/SSHJAuthentication.java @@ -1,6 +1,5 @@ package com.plugin.sshjplugin.model; import com.dtolabs.rundeck.plugins.PluginLogger; -import com.dtolabs.utils.Streams; import com.plugin.sshjplugin.SSHJBuilder; import net.schmizz.sshj.SSHClient; import net.schmizz.sshj.common.Factory; @@ -64,11 +63,14 @@ void authenticate(final SSHClient ssh) throws IOException { } KeyFormat format = KeyProviderUtil.detectKeyFileFormat(privateKeyContent,true); keys = Factory.Named.Util.create(ssh.getTransport().getConfig().getFileKeyProviderFactories(), format.toString()); - if (passphrase == null) { - keys.init(new StringReader(privateKeyContent), null); - } else { - logger.log(3, "[sshj-debug] Using Passphrase: " + passphrasePath); - keys.init(new StringReader(privateKeyContent), PasswordUtils.createOneOff(passphrase.toCharArray())); + + if(keys != null ){ + if (passphrase == null) { + keys.init(new StringReader(privateKeyContent), null); + } else { + logger.log(3, "[sshj-debug] Using Passphrase: " + passphrasePath); + keys.init(new StringReader(privateKeyContent), PasswordUtils.createOneOff(passphrase.toCharArray())); + } } } ssh.authPublickey(username, keys); diff --git a/src/test/groovy/com/plugin/sshjplugin/model/SSHJAuthenticationTest.groovy b/src/test/groovy/com/plugin/sshjplugin/model/SSHJAuthenticationTest.groovy index 17e9f94..ab1d500 100644 --- a/src/test/groovy/com/plugin/sshjplugin/model/SSHJAuthenticationTest.groovy +++ b/src/test/groovy/com/plugin/sshjplugin/model/SSHJAuthenticationTest.groovy @@ -8,9 +8,6 @@ import net.schmizz.sshj.common.Factory import net.schmizz.sshj.transport.Transport import net.schmizz.sshj.userauth.keyprovider.FileKeyProvider import spock.lang.Specification -import net.schmizz.sshj.userauth.keyprovider.PKCS8KeyFile; -import net.schmizz.sshj.userauth.keyprovider.OpenSSHKeyFile; -import net.schmizz.sshj.userauth.keyprovider.PuTTYKeyFile class SSHJAuthenticationTest extends Specification { @@ -62,7 +59,7 @@ class SSHJAuthenticationTest extends Specification { - /* def "authenticate with private key Rundeck storage no passphrase"() { + def "authenticate with private key Rundeck storage no passphrase"() { given: String keyStoragePath = "keys/rundeck/storage" SSHClient sshClient = Mock(SSHClient){ @@ -96,17 +93,21 @@ class SSHJAuthenticationTest extends Specification { then: true - }*/ - - - public static List > providerFactoriesList() { - List> factories = new ArrayList<>(); - factories.add(OpenSSHKeyV1KeyFile); - factories.add(PKCS8KeyFile); - factories.add(OpenSSHKeyFile); - factories.add(PuTTYKeyFile); + } - return factories; + private static List> providerFactoriesList() { + List> namedList = new ArrayList<>(); + namedList.add(new Factory.Named(){ + @Override + FileKeyProvider create() { + return new OpenSSHKeyV1KeyFile(); + } + @Override + String getName() { + return "OpenSSHKeyV1KeyFile" + } + }) + return namedList; } From ae1a798344efd599887ad565fdbbfed13db1e5fa Mon Sep 17 00:00:00 2001 From: Jesus Osuna Date: Wed, 6 Sep 2023 14:59:29 -0300 Subject: [PATCH 09/10] add missing tests --- .../model/SSHJAuthenticationTest.groovy | 85 ++++++++++++++++++- 1 file changed, 83 insertions(+), 2 deletions(-) diff --git a/src/test/groovy/com/plugin/sshjplugin/model/SSHJAuthenticationTest.groovy b/src/test/groovy/com/plugin/sshjplugin/model/SSHJAuthenticationTest.groovy index ab1d500..6464904 100644 --- a/src/test/groovy/com/plugin/sshjplugin/model/SSHJAuthenticationTest.groovy +++ b/src/test/groovy/com/plugin/sshjplugin/model/SSHJAuthenticationTest.groovy @@ -86,15 +86,96 @@ class SSHJAuthenticationTest extends Specification { } SSHJAuthentication auth = new SSHJAuthentication(connectionParameters,pluginLogger) - OpenSSHKeyV1KeyFile when: auth.authenticate(sshClient) then: - true + 1 * sshClient.authPublickey(_,_) + + } + + def "authenticate with private key Rundeck storage and passphrase"() { + given: + String keyStoragePath = "keys/rundeck/storage" + String passphraseStoragePath = "keys/rundeck/storage/passphrase" + SSHClient sshClient = Mock(SSHClient){ + getTransport() >> Mock(Transport){ + getConfig() >> Mock(Config){ + getFileKeyProviderFactories() >> providerFactoriesList() + } + } + } + PluginLogger pluginLogger = Mock(PluginLogger) + SSHJConnection connectionParameters = Mock(SSHJConnection){ + 1 * getPrivateKeyPassphraseStoragePath() >> passphraseStoragePath + 1 * getAuthenticationType() >> SSHJConnection.AuthenticationType.privateKey + 1 * getPrivateKeyStoragePath() >> keyStoragePath + 1 * getPrivateKeyStorage(keyStoragePath) >> "-----BEGIN OPENSSH PRIVATE KEY-----\n" + + "b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABCBHpyIOm\n" + + "Y45NDeuHxAzGCUAAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIKUYWSH2YrYUH3IA\n" + + "t40IcM0ykM03oFRI7m+5jEK+fE4LAAAAoBIhOVxejwLYvIKIBgNQOe0j8h2nnz/+sEYUDc\n" + + "ug6KrlPxQ7kuL67It/Tb7IxAGzVWT3g3fkQMGNU/8uxRHAf5fQC9aYValFPr21g7I39OqR\n" + + "MbPXHnD8a+DwAw3ArakcZigzWqncuX5cuBgpr5+x/iXWAz0lAHJH1d5HaIsoy1K6VmMR+b\n" + + "GN7ixrjWwMVBM+Lv8DdRN5UnniX5grj6M8P0A=\n" + + "-----END OPENSSH PRIVATE KEY-----" + 1 * getPrivateKeyPassphrase(_) + 0 * getPrivateKeyStorage(_) + } + + SSHJAuthentication auth = new SSHJAuthentication(connectionParameters,pluginLogger) + + when: + auth.authenticate(sshClient) + + then: + 1 * sshClient.authPublickey(_,_) + } + + + def "if local storage path and rundeck storage path are provided, key stored on rundeck will be used"() { + given: + String keyStoragePath = "keys/rundeck/storage" + String fileSystemStoragePath = "user/key" + String passphraseStoragePath = "keys/rundeck/storage/passphrase" + SSHClient sshClient = Mock(SSHClient){ + getTransport() >> Mock(Transport){ + getConfig() >> Mock(Config){ + getFileKeyProviderFactories() >> providerFactoriesList() + } + } + } + PluginLogger pluginLogger = Mock(PluginLogger) + SSHJConnection connectionParameters = Mock(SSHJConnection){ + 1 * getPrivateKeyFilePath() >> fileSystemStoragePath + 1 * getPrivateKeyPassphraseStoragePath() >> passphraseStoragePath + 1 * getAuthenticationType() >> SSHJConnection.AuthenticationType.privateKey + 1 * getPrivateKeyStoragePath() >> keyStoragePath + 1 * getPrivateKeyStorage(keyStoragePath) >> "-----BEGIN OPENSSH PRIVATE KEY-----\n" + + "b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABCBHpyIOm\n" + + "Y45NDeuHxAzGCUAAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIKUYWSH2YrYUH3IA\n" + + "t40IcM0ykM03oFRI7m+5jEK+fE4LAAAAoBIhOVxejwLYvIKIBgNQOe0j8h2nnz/+sEYUDc\n" + + "ug6KrlPxQ7kuL67It/Tb7IxAGzVWT3g3fkQMGNU/8uxRHAf5fQC9aYValFPr21g7I39OqR\n" + + "MbPXHnD8a+DwAw3ArakcZigzWqncuX5cuBgpr5+x/iXWAz0lAHJH1d5HaIsoy1K6VmMR+b\n" + + "GN7ixrjWwMVBM+Lv8DdRN5UnniX5grj6M8P0A=\n" + + "-----END OPENSSH PRIVATE KEY-----" + 1 * getPrivateKeyPassphrase(_) + 0 * getPrivateKeyStorage(_) + } + + SSHJAuthentication auth = new SSHJAuthentication(connectionParameters,pluginLogger) + + when: + auth.authenticate(sshClient) + + then: + 1 * sshClient.authPublickey(_,_) + 0 * sshClient.loadKeys(_) + 0 * sshClient.loadKeys(_, _) } + + private static List> providerFactoriesList() { List> namedList = new ArrayList<>(); namedList.add(new Factory.Named(){ From f93e01bc428d1c5970a0758aa50dbe6aeb98c10f Mon Sep 17 00:00:00 2001 From: Russ Robinson Date: Mon, 11 Sep 2023 12:47:19 -0400 Subject: [PATCH 10/10] re-base --- .../sshjplugin/model/SSHJAuthentication.class | Bin 5110 -> 5594 bytes .../sshjplugin/model/SSHJConnection.class | Bin 1439 -> 1471 bytes .../model/SSHJConnectionParameters.class | Bin 9115 -> 9115 bytes .../model/SSHJAuthenticationTest.groovy | 196 ++++++++++++++++++ 4 files changed, 196 insertions(+) create mode 100644 bin/test/com/plugin/sshjplugin/model/SSHJAuthenticationTest.groovy diff --git a/bin/main/com/plugin/sshjplugin/model/SSHJAuthentication.class b/bin/main/com/plugin/sshjplugin/model/SSHJAuthentication.class index 9fc0bb60cfb524461bfca9817e7b613a96387143..d547ce3a9963d9d770eaef47f531ef263293ff03 100644 GIT binary patch delta 2155 zcmZ{ld302F6~{lnpe zz4WrlO+*pgoJaUg&<|U4=kMY!@e+%N z`{g{TSW~s`wXj-rgL|f~_XUj>iy^($b7PU?B5t#b5k*K3*_C>UyCkpTCZTWi&Rgat zrI`PJu=J+liEuO$yel%=9CYfp$L4OjwQ|iW@}L;C@#1##Rz2og=6O45i+3m*^*z1@ zPhV;Lr~hg#EWDo&=n8l3f)DYa#RF38;Yg}J zxnp2xaNoXqr$F^>@!_3=QJaT&SSQ?#9!=Qd5kSc`2TYOI8e{Cggo@89%OU2s; z!ik#_iSVexs9LzW&C3`0lEqVs#ael5yiSryn=kVf{eri{cv{imB=2iPfM88wc<2D|roG0}>yFp6!oXzumO>eea z)M@>gz4GSLk*iaEr8UlAE6S$j&b0TACR352{890U1Q;1egp;`?(49qXB?q7B5#i5levYZ|P6xKQ zpqCckVO&yF=pI*v-d|j;ey`6JR~vs+%$2^()m1abN@rnECVm1G^LnAKXCAZl#ThHp zXEAX@!Q4rfRv)G+L(`(Cgr6yUuH;uBzfvKemcuf$WJQ)$g$P}qWwoL+PpwgO<*8d0 z&4y}r1&k>cS39zRk)^YMXXqJcPP2KG-q2C{LM0m~2nCFEhAkPkO|au+-4t7E&#+01 z%0x|6`bDKKukdQvo-_DsM7Hx;V$H5`YHR95zbC_RPB3lzmYn-*jBBTunm$g-ajl$6 z{B3#Pa#miOWw!%$?>Musfx7RC@)e+>x!0KFZOvv)hIeIouOiC_6#+BDhbQ?+4oSc@ z#YWK-z=444D36C6vk5+_vV5wLahioMGt;?5{|D@USvX!YJTb`^a;7!H&ohqm)lgx; z)th1eI8hNvLk>&v&44+@oSg5R>j|e00?euMbfN(>OV-IWeaz{Kb+s!eu2FZIk_#r@ zj%9fE0EKdqZYRPiUQs?(%qewAy{!H&yv&3Q$`e${vw|RRpqBYGv4B=8>EH&sSSZ#t zVqYtJS|^v(V(uf|AVy1M!IyGK-YXdz#aR;$}owTV9 zGJGL!Q8C(8oHgox)~Y92rzYrBM_8{;%ch^DM_uAJ^)h|xAKYG0La1O4TLc1($m>RC zp4zk^k&lp5oRNWVQup$8S%?A&wvTTJRY-?~I4hJvmjwJK-@+vs1o$@Jk#+RTI_0S) zq{F>j$Wwcxz&-`s`fZy>4{v=o2qeA@| G&wl{lz8Jg! delta 1786 zcmY*ZX>=1+6#iZ^nMo$o5H?LpThbnduCxecNkOaS2ol;93PP-C2b-h~h14Vk3PP;n zf`W<_3SwbDdO)8j&U90+#*+6y3i=$A%=3k*;a0ru^x{~c!aN$%Z-oW zao#P@U{6rYDPA)+GPoL}(UwqIvwulPaItGalu~n%#52F zxIB~mJiprPkI2}hm0$xlD|i;1kFwSq&DhF+lM1S3JdYP8yue`Bp>68&;D2BRFX3ed zYb;#TqDF3wMAS|Op|Gg2M#d}HF5y*%3gVv4Pgots9e9mjvU-G_4C6^z#Lt+`)SfWj zRPYuOJft)UZ!6f%Pb<0XUH*$QCjWf}A83uRYK@@suM9_;wc_tlV9|=dk59-NZw}*t zf+Tu)TUHfI^IcifhuGQ&$EaS7weS{O-Y8r9U;{NBgBS=$mZ>qFj#A9qWsz0!v9=n1 z+?H=Yslbk*GA82_1*h;S|HC%V^|=;Tw2<-(1!o{K$X5eN_>u>PEMnjABSY%g_uOVL z7Jg(H&Aa5$ywSdMqyqw+D2U?PhO3Ag=q6hIGv_GZ1Rzrrw327G@dacfvFlSF6y`~SEK5k8}t4OAvP@^K$Tu^IWxB;aY zL)L|4UxZoow+Us0Dkoe8)?z%i(jwfBcqMvirp{w3zJnXTq6)ub8na?L8;e`m6jZY+ z+{SK44f9aL1DMG=FpI6m9gJfR+fFO4ghB>rH_{AI&@z&=Q!n~xekL&!4$^!Xh=K!$ zaF~dOa27{MXCxYqxULk4$u=Cthcu?W=)y4^hlzsv@DUk`6wayJNjO2?mqKw3PJS(k(jl>-y*6cBt{A3T%dL1;g zo9QxEQYs_JCQhWaOHr{G{l0z(-3wFr`0zp5Q@j_S(akY=6A%`XaGC@mBlz~AzncnR zryO!9^;}|JMBhxunnc~9O|PPM*R%wkFwK&1PNPeSozq@t;n7~?=R6r0Z~Ofr-V4k->JN zfYxMx7Gp-K$;~W*^=u3bEQ|~y41x@d3|v41Sb#J)P(+jgA|r+&BMz03z>tw-kOG?~ z4OJlnrDYl9z~b@@3Se3hN-Hragn$KR4KQDmK?~?CZJ_g*7<7P+ Z(*+p}Wb1)ReFg&{%>`6p2*kz?k^q=08Pos( delta 250 zcmdnbJ)fKF)W2Q(7#J8#7>qY^?PJ{hfH9JZ#gLJ~cA|jR#J$3kt5_5mB`42j39J`n zU}WF|s$>Du+$@X?A`EOm4kLpo1G6tqL_rjX@pE*I>{DI!g=aJSGNhprdp^1_Rl;U{a4kA4qcnRTu!V Hk%J@v*!L8y diff --git a/bin/main/com/plugin/sshjplugin/model/SSHJConnectionParameters.class b/bin/main/com/plugin/sshjplugin/model/SSHJConnectionParameters.class index 7421d0bf89d497e59642c92b985842620e06dd6a..acd93c989e5c1e80c5c410330d9f7ea9f4c2b4bf 100644 GIT binary patch delta 417 zcmW;HNhri&9LDkI#|#Y_q<_qq&EV!hjiO{Z;D~Z^nbeS-k`hIfB`5i1sSy_!nKI>q zgNw@y$z^aF%f)3Z2V*(m;qd++U*D!0c4;94s@Xy zy(mW?YSFI`jy4(6>U_U30y9Qo!5FeJfjmsYiJ)%h2Q49%DVAxjXSK%JFLS!#^cxF2 zU*!1`7t36PxrnHzARsFmD)1Yt>}%}n?3?Uc>^r=%Qp;UF(;myd_PUznXx?q;rK?Pi z=iSZL6PR$yF@ie8G)x xl;H<;0> SSHJConnection.AuthenticationType.privateKey + 1 * getPrivateKeyFilePath() >> "keys/rundeck/storage" + 0 * getPrivateKeyPassphrase(_) + 0 * getPrivateKeyStorage(_) + } + + SSHJAuthentication auth = new SSHJAuthentication(connectionParameters,pluginLogger) + + when: + auth.authenticate(sshClient) + + then: + 1 * sshClient.authPublickey(_, _) + 1 * sshClient.loadKeys(_) + 0 * sshClient.loadKeys(_, _) + } + + def "authenticate with private key on filesystem with passphrase"() { + given: + String passphraseStoragePath = "keys/rundeck/storage/passphrase" + + SSHClient sshClient = Mock(SSHClient) + PluginLogger pluginLogger = Mock(PluginLogger) + SSHJConnection connectionParameters = Mock(SSHJConnection){ + 1 * getAuthenticationType() >> SSHJConnection.AuthenticationType.privateKey + 1 * getPrivateKeyFilePath() >> "keys/rundeck/storage" + 1 * getPrivateKeyPassphraseStoragePath() >> passphraseStoragePath + 1 * getPrivateKeyPassphrase(passphraseStoragePath) >> "pass" + 0 * getPrivateKeyStorage(_) + } + SSHJAuthentication auth = new SSHJAuthentication(connectionParameters,pluginLogger) + + when: + auth.authenticate(sshClient) + + then: + 0 * sshClient.loadKeys(_) + 1 * sshClient.authPublickey(_, _) + } + + + + def "authenticate with private key Rundeck storage no passphrase"() { + given: + String keyStoragePath = "keys/rundeck/storage" + SSHClient sshClient = Mock(SSHClient){ + getTransport() >> Mock(Transport){ + getConfig() >> Mock(Config){ + getFileKeyProviderFactories() >> providerFactoriesList() + } + } + } + PluginLogger pluginLogger = Mock(PluginLogger) + SSHJConnection connectionParameters = Mock(SSHJConnection){ + 1 * getAuthenticationType() >> SSHJConnection.AuthenticationType.privateKey + 1 * getPrivateKeyStoragePath() >> keyStoragePath + 1 * getPrivateKeyStorage(keyStoragePath) >> "-----BEGIN OPENSSH PRIVATE KEY-----\n" + + "b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABCBHpyIOm\n" + + "Y45NDeuHxAzGCUAAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIKUYWSH2YrYUH3IA\n" + + "t40IcM0ykM03oFRI7m+5jEK+fE4LAAAAoBIhOVxejwLYvIKIBgNQOe0j8h2nnz/+sEYUDc\n" + + "ug6KrlPxQ7kuL67It/Tb7IxAGzVWT3g3fkQMGNU/8uxRHAf5fQC9aYValFPr21g7I39OqR\n" + + "MbPXHnD8a+DwAw3ArakcZigzWqncuX5cuBgpr5+x/iXWAz0lAHJH1d5HaIsoy1K6VmMR+b\n" + + "GN7ixrjWwMVBM+Lv8DdRN5UnniX5grj6M8P0A=\n" + + "-----END OPENSSH PRIVATE KEY-----" + 0 * getPrivateKeyPassphrase(_) + 0 * getPrivateKeyStorage(_) + } + + SSHJAuthentication auth = new SSHJAuthentication(connectionParameters,pluginLogger) + + when: + auth.authenticate(sshClient) + + then: + 1 * sshClient.authPublickey(_,_) + + } + + def "authenticate with private key Rundeck storage and passphrase"() { + given: + String keyStoragePath = "keys/rundeck/storage" + String passphraseStoragePath = "keys/rundeck/storage/passphrase" + SSHClient sshClient = Mock(SSHClient){ + getTransport() >> Mock(Transport){ + getConfig() >> Mock(Config){ + getFileKeyProviderFactories() >> providerFactoriesList() + } + } + } + PluginLogger pluginLogger = Mock(PluginLogger) + SSHJConnection connectionParameters = Mock(SSHJConnection){ + 1 * getPrivateKeyPassphraseStoragePath() >> passphraseStoragePath + 1 * getAuthenticationType() >> SSHJConnection.AuthenticationType.privateKey + 1 * getPrivateKeyStoragePath() >> keyStoragePath + 1 * getPrivateKeyStorage(keyStoragePath) >> "-----BEGIN OPENSSH PRIVATE KEY-----\n" + + "b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABCBHpyIOm\n" + + "Y45NDeuHxAzGCUAAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIKUYWSH2YrYUH3IA\n" + + "t40IcM0ykM03oFRI7m+5jEK+fE4LAAAAoBIhOVxejwLYvIKIBgNQOe0j8h2nnz/+sEYUDc\n" + + "ug6KrlPxQ7kuL67It/Tb7IxAGzVWT3g3fkQMGNU/8uxRHAf5fQC9aYValFPr21g7I39OqR\n" + + "MbPXHnD8a+DwAw3ArakcZigzWqncuX5cuBgpr5+x/iXWAz0lAHJH1d5HaIsoy1K6VmMR+b\n" + + "GN7ixrjWwMVBM+Lv8DdRN5UnniX5grj6M8P0A=\n" + + "-----END OPENSSH PRIVATE KEY-----" + 1 * getPrivateKeyPassphrase(_) + 0 * getPrivateKeyStorage(_) + } + + SSHJAuthentication auth = new SSHJAuthentication(connectionParameters,pluginLogger) + + when: + auth.authenticate(sshClient) + + then: + 1 * sshClient.authPublickey(_,_) + } + + + def "if local storage path and rundeck storage path are provided, key stored on rundeck will be used"() { + given: + String keyStoragePath = "keys/rundeck/storage" + String fileSystemStoragePath = "user/key" + String passphraseStoragePath = "keys/rundeck/storage/passphrase" + SSHClient sshClient = Mock(SSHClient){ + getTransport() >> Mock(Transport){ + getConfig() >> Mock(Config){ + getFileKeyProviderFactories() >> providerFactoriesList() + } + } + } + PluginLogger pluginLogger = Mock(PluginLogger) + SSHJConnection connectionParameters = Mock(SSHJConnection){ + 1 * getPrivateKeyFilePath() >> fileSystemStoragePath + 1 * getPrivateKeyPassphraseStoragePath() >> passphraseStoragePath + 1 * getAuthenticationType() >> SSHJConnection.AuthenticationType.privateKey + 1 * getPrivateKeyStoragePath() >> keyStoragePath + 1 * getPrivateKeyStorage(keyStoragePath) >> "-----BEGIN OPENSSH PRIVATE KEY-----\n" + + "b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABCBHpyIOm\n" + + "Y45NDeuHxAzGCUAAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIKUYWSH2YrYUH3IA\n" + + "t40IcM0ykM03oFRI7m+5jEK+fE4LAAAAoBIhOVxejwLYvIKIBgNQOe0j8h2nnz/+sEYUDc\n" + + "ug6KrlPxQ7kuL67It/Tb7IxAGzVWT3g3fkQMGNU/8uxRHAf5fQC9aYValFPr21g7I39OqR\n" + + "MbPXHnD8a+DwAw3ArakcZigzWqncuX5cuBgpr5+x/iXWAz0lAHJH1d5HaIsoy1K6VmMR+b\n" + + "GN7ixrjWwMVBM+Lv8DdRN5UnniX5grj6M8P0A=\n" + + "-----END OPENSSH PRIVATE KEY-----" + 1 * getPrivateKeyPassphrase(_) + 0 * getPrivateKeyStorage(_) + } + + SSHJAuthentication auth = new SSHJAuthentication(connectionParameters,pluginLogger) + + when: + auth.authenticate(sshClient) + + then: + 1 * sshClient.authPublickey(_,_) + 0 * sshClient.loadKeys(_) + 0 * sshClient.loadKeys(_, _) + } + + + + private static List> providerFactoriesList() { + List> namedList = new ArrayList<>(); + namedList.add(new Factory.Named(){ + @Override + FileKeyProvider create() { + return new OpenSSHKeyV1KeyFile(); + } + @Override + String getName() { + return "OpenSSHKeyV1KeyFile" + } + }) + return namedList; + } + + + } +