From b6143efab2f45ff11c980438c60d79f6db0da868 Mon Sep 17 00:00:00 2001 From: Emmanuel Bourg Date: Thu, 24 Oct 2019 17:00:21 +0200 Subject: [PATCH] Support the SSH configuration file (~/.ssh/config) in the sshexec, sshsession and scp tasks This closes #106 pull request at github/apache/ant repo --- WHATSNEW | 4 ++ manual/Tasks/scp.html | 8 +++ manual/Tasks/sshexec.html | 8 +++ manual/Tasks/sshsession.html | 8 +++ .../ant/taskdefs/optional/ssh/SSHBase.java | 55 +++++++++++++++++++ .../ant/taskdefs/optional/ssh/SSHExec.java | 3 + .../ant/taskdefs/optional/ssh/SSHSession.java | 3 + .../tools/ant/taskdefs/optional/ssh/Scp.java | 17 +++--- 8 files changed, 99 insertions(+), 7 deletions(-) diff --git a/WHATSNEW b/WHATSNEW index ad4ac9eba..29dedcf60 100644 --- a/WHATSNEW +++ b/WHATSNEW @@ -14,6 +14,10 @@ Fixed bugs: files. Bugzilla Report 63874 + * sshexec, sshsession and scp now support a new sshConfig parameter. + It specified the SSH configuration file (typically ${user.home}/.ssh/config) + defining the username and keyfile to be used per host. + Other changes: -------------- diff --git a/manual/Tasks/scp.html b/manual/Tasks/scp.html index c353879a3..a9a95a12d 100644 --- a/manual/Tasks/scp.html +++ b/manual/Tasks/scp.html @@ -142,6 +142,14 @@ information. This task has been tested with jsch-0.1.2 and later.< Passphrase for your private key. No; defaults to an empty string + + sshConfig + Location of the file holding the OpenSSH style configuration (e.g. ${user.home}/.ssh/config). + The username and the key file are read from the configuration file, + unless they are already specified in the task parameters. + since Ant 1.10.8 + No + verbose Determines whether SCP outputs verbosely to the user. Currently this means outputting diff --git a/manual/Tasks/sshexec.html b/manual/Tasks/sshexec.html index c30b4a5e2..6437b4608 100644 --- a/manual/Tasks/sshexec.html +++ b/manual/Tasks/sshexec.html @@ -104,6 +104,14 @@ JSCh earlier than 0.1.28.

Passphrase for your private key. No; defaults to an empty string + + sshConfig + Location of the file holding the OpenSSH style configuration (e.g. ${user.home}/.ssh/config). + The username and the key file are read from the configuration file, + unless they are already specified in the task parameters. + since Ant 1.10.8 + No + suppresssystemout Whether to suppress system out. since Ant 1.9.0 diff --git a/manual/Tasks/sshsession.html b/manual/Tasks/sshsession.html index bf8d80ac2..6f557bb72 100644 --- a/manual/Tasks/sshsession.html +++ b/manual/Tasks/sshsession.html @@ -110,6 +110,14 @@ JSCh earlier than 0.1.28.

Passphrase for your private key. No; defaults to an empty string + + sshConfig + Location of the file holding the OpenSSH style configuration (e.g. ${user.home}/.ssh/config). + The username and the key file are read from the configuration file, + unless they are already specified in the task parameters. + since Ant 1.10.8 + No + timeout Give up if the connection cannot be established within the specified time (given in diff --git a/src/main/org/apache/tools/ant/taskdefs/optional/ssh/SSHBase.java b/src/main/org/apache/tools/ant/taskdefs/optional/ssh/SSHBase.java index 338a11a38..edee9a8ec 100644 --- a/src/main/org/apache/tools/ant/taskdefs/optional/ssh/SSHBase.java +++ b/src/main/org/apache/tools/ant/taskdefs/optional/ssh/SSHBase.java @@ -18,6 +18,11 @@ package org.apache.tools.ant.taskdefs.optional.ssh; +import java.io.File; +import java.io.IOException; + +import com.jcraft.jsch.ConfigRepository; +import com.jcraft.jsch.OpenSSHConfig; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.Project; import org.apache.tools.ant.Task; @@ -42,6 +47,7 @@ public abstract class SSHBase extends Task implements LogListener { private boolean failOnError = true; private boolean verbose; private final SSHUserInfo userInfo; + private String sshConfig; private int serverAliveCountMax = 3; private int serverAliveIntervalSeconds = 0; @@ -106,6 +112,24 @@ public abstract class SSHBase extends Task implements LogListener { return verbose; } + /** + * Get the OpenSSH config file (~/.ssh/config). + * @return the OpenSSH config file + * @since Ant 1.10.8 + */ + public String getSshConfig() { + return sshConfig; + } + + /** + * Set the OpenSSH config file (~/.ssh/config). + * @param sshConfig the OpenSSH config file + * @since Ant 1.10.8 + */ + public void setSshConfig(String sshConfig) { + this.sshConfig = sshConfig; + } + /** * Set the serverAliveCountMax value. * @param countMax int @@ -235,6 +259,37 @@ public abstract class SSHBase extends Task implements LogListener { this.port = SSH_PORT; } + /** + * Load the SSH configuration file. + * @throws BuildException on error + */ + protected void loadSshConfig() throws BuildException { + if (sshConfig != null && (userInfo.getName() == null || userInfo.getKeyfile() == null)) { + if (!new File(sshConfig).exists()) { + throw new BuildException("The SSH configuration file specified doesn't exist: " + sshConfig); + } + + log("Loading SSH configuration file " + sshConfig, Project.MSG_DEBUG); + ConfigRepository.Config config = null; + try { + config = OpenSSHConfig.parseFile(sshConfig).getConfig(host); + } catch (IOException e) { + throw new BuildException("Failed to load the SSH configuration file " + sshConfig, e); + } + + host = config.getHostname(); + + if (userInfo.getName() == null) { + userInfo.setName(config.getUser()); + } + + if (userInfo.getKeyfile() == null) { + log("Using SSH key file " + config.getValue("IdentityFile") + " for host " + host, Project.MSG_INFO); + userInfo.setKeyfile(config.getValue("IdentityFile")); + } + } + } + /** * Open an ssh session. * @return the opened session diff --git a/src/main/org/apache/tools/ant/taskdefs/optional/ssh/SSHExec.java b/src/main/org/apache/tools/ant/taskdefs/optional/ssh/SSHExec.java index 1cd273b54..17dd4313b 100644 --- a/src/main/org/apache/tools/ant/taskdefs/optional/ssh/SSHExec.java +++ b/src/main/org/apache/tools/ant/taskdefs/optional/ssh/SSHExec.java @@ -284,6 +284,9 @@ public class SSHExec extends SSHBase { if (getHost() == null) { throw new BuildException("Host is required."); } + + loadSshConfig(); + if (getUserInfo().getName() == null) { throw new BuildException("Username is required."); } diff --git a/src/main/org/apache/tools/ant/taskdefs/optional/ssh/SSHSession.java b/src/main/org/apache/tools/ant/taskdefs/optional/ssh/SSHSession.java index 6b7c33ef6..5c5224658 100644 --- a/src/main/org/apache/tools/ant/taskdefs/optional/ssh/SSHSession.java +++ b/src/main/org/apache/tools/ant/taskdefs/optional/ssh/SSHSession.java @@ -132,6 +132,9 @@ public class SSHSession extends SSHBase { if (getHost() == null) { throw new BuildException("Host is required."); } + + loadSshConfig(); + if (getUserInfo().getName() == null) { throw new BuildException("Username is required."); } diff --git a/src/main/org/apache/tools/ant/taskdefs/optional/ssh/Scp.java b/src/main/org/apache/tools/ant/taskdefs/optional/ssh/Scp.java index f954a72f4..3d181d30e 100644 --- a/src/main/org/apache/tools/ant/taskdefs/optional/ssh/Scp.java +++ b/src/main/org/apache/tools/ant/taskdefs/optional/ssh/Scp.java @@ -435,19 +435,22 @@ public class Scp extends SSHBase { throw new BuildException("no username was given. Can't authenticate."); } - if (getUserInfo().getPassword() == null - && getUserInfo().getKeyfile() == null) { - throw new BuildException( - "neither password nor keyfile for user %s has been given. Can't authenticate.", - getUserInfo().getName()); - } - final int indexOfPath = uri.indexOf(':', indexOfAt + 1); if (indexOfPath == -1) { throw new BuildException("no remote path in %s", uri); } setHost(uri.substring(indexOfAt + 1, indexOfPath)); + + loadSshConfig(); + + if (getUserInfo().getPassword() == null + && getUserInfo().getKeyfile() == null) { + throw new BuildException( + "neither password nor keyfile for user %s has been given. Can't authenticate.", + getUserInfo().getName()); + } + String remotePath = uri.substring(indexOfPath + 1); if (remotePath.isEmpty()) { remotePath = ".";