提交 492dbbed 编写于 作者: J Jesse Glick

[JENKINS-41745] Make jenkins-cli.jar connect to the SSH port by default.

上级 6e39dd35
...@@ -50,6 +50,17 @@ ...@@ -50,6 +50,17 @@
<version>1.24</version> <version>1.24</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.sshd</groupId>
<artifactId>sshd-core</artifactId>
<version>1.2.0</version> <!-- TODO 1.3.0 requires Java 8 -->
<optional>true</optional> <!-- do not expose to core -->
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-nop</artifactId>
<optional>true</optional> <!-- ditto -->
</dependency>
<dependency> <!-- TODO remove and replace PrivateKeyProvider with SecurityUtils.createFileKeyPairProvider() as in SshClient -->
<groupId>org.jenkins-ci</groupId> <groupId>org.jenkins-ci</groupId>
<artifactId>trilead-ssh2</artifactId> <artifactId>trilead-ssh2</artifactId>
<version>build214-jenkins-1</version> <version>build214-jenkins-1</version>
......
...@@ -32,6 +32,7 @@ import hudson.remoting.RemoteInputStream; ...@@ -32,6 +32,7 @@ import hudson.remoting.RemoteInputStream;
import hudson.remoting.RemoteOutputStream; import hudson.remoting.RemoteOutputStream;
import hudson.remoting.SocketChannelStream; import hudson.remoting.SocketChannelStream;
import hudson.remoting.SocketOutputStream; import hudson.remoting.SocketOutputStream;
import hudson.util.QuotedStringTokenizer;
import javax.crypto.SecretKey; import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec; import javax.crypto.spec.SecretKeySpec;
...@@ -57,6 +58,8 @@ import java.io.StringReader; ...@@ -57,6 +58,8 @@ import java.io.StringReader;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.Socket; import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketTimeoutException;
import java.net.URL; import java.net.URL;
import java.net.URLConnection; import java.net.URLConnection;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
...@@ -70,11 +73,23 @@ import java.util.Collections; ...@@ -70,11 +73,23 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Properties; import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import static java.util.logging.Level.*; import static java.util.logging.Level.*;
import org.apache.sshd.client.SshClient;
import org.apache.sshd.client.channel.ClientChannel;
import org.apache.sshd.client.channel.ClientChannelEvent;
import org.apache.sshd.client.future.ConnectFuture;
import org.apache.sshd.client.keyverifier.DefaultKnownHostsServerKeyVerifier;
import org.apache.sshd.client.keyverifier.KnownHostsServerKeyVerifier;
import org.apache.sshd.client.keyverifier.ServerKeyVerifier;
import org.apache.sshd.client.session.ClientSession;
import org.apache.sshd.common.future.WaitableFuture;
import org.apache.sshd.common.util.io.NoCloseInputStream;
import org.apache.sshd.common.util.io.NoCloseOutputStream;
/** /**
* CLI entry point to Jenkins. * CLI entry point to Jenkins.
...@@ -403,12 +418,21 @@ public class CLI implements AutoCloseable { ...@@ -403,12 +418,21 @@ public class CLI implements AutoCloseable {
boolean tryLoadPKey = true; boolean tryLoadPKey = true;
boolean useRemoting = false;
String user = null;
while(!args.isEmpty()) { while(!args.isEmpty()) {
String head = args.get(0); String head = args.get(0);
if (head.equals("-version")) { if (head.equals("-version")) {
System.out.println("Version: "+computeVersion()); System.out.println("Version: "+computeVersion());
return 0; return 0;
} }
if (head.equals("-remoting")) {
useRemoting = true;
args = args.subList(1,args.size());
continue;
}
if(head.equals("-s") && args.size()>=2) { if(head.equals("-s") && args.size()>=2) {
url = args.get(1); url = args.get(1);
args = args.subList(2,args.size()); args = args.subList(2,args.size());
...@@ -446,6 +470,11 @@ public class CLI implements AutoCloseable { ...@@ -446,6 +470,11 @@ public class CLI implements AutoCloseable {
sshAuthRequestedExplicitly = true; sshAuthRequestedExplicitly = true;
continue; continue;
} }
if (head.equals("-user") && args.size() >= 2) {
user = args.get(1);
args = args.subList(2, args.size());
continue;
}
if(head.equals("-p") && args.size()>=2) { if(head.equals("-p") && args.size()>=2) {
httpProxy = args.get(1); httpProxy = args.get(1);
args = args.subList(2,args.size()); args = args.subList(2,args.size());
...@@ -465,6 +494,19 @@ public class CLI implements AutoCloseable { ...@@ -465,6 +494,19 @@ public class CLI implements AutoCloseable {
if (tryLoadPKey && !provider.hasKeys()) if (tryLoadPKey && !provider.hasKeys())
provider.readFromDefaultLocations(); provider.readFromDefaultLocations();
if (!useRemoting) {
if (user == null) {
// TODO SshCliAuthenticator already autodetects the user based on public key; why cannot AsynchronousCommand.getCurrentUser do the same?
System.err.println("-user required when not using -remoting");
return -1;
}
return sshConnection(url, user, args, provider);
}
if (user != null) {
System.err.println("Warning: -user ignored when using -remoting");
}
CLIConnectionFactory factory = new CLIConnectionFactory().url(url).httpsProxyTunnel(httpProxy); CLIConnectionFactory factory = new CLIConnectionFactory().url(url).httpsProxyTunnel(httpProxy);
String userInfo = new URL(url).getUserInfo(); String userInfo = new URL(url).getUserInfo();
if (userInfo != null) { if (userInfo != null) {
...@@ -507,6 +549,75 @@ public class CLI implements AutoCloseable { ...@@ -507,6 +549,75 @@ public class CLI implements AutoCloseable {
} }
} }
private static int sshConnection(String jenkinsUrl, String user, List<String> args, PrivateKeyProvider provider) throws IOException {
URL url = new URL(jenkinsUrl + "/login");
URLConnection conn = url.openConnection();
String endpointDescription = conn.getHeaderField("X-SSH-Endpoint");
if (endpointDescription == null) {
System.err.println("No header 'X-SSH-Endpoint' returned by Jenkins");
return -1;
}
System.err.println("Connecting to: " + endpointDescription);
int sshPort = Integer.valueOf(endpointDescription.split(":")[1]);
String sshHost = endpointDescription.split(":")[0];
StringBuilder command = new StringBuilder();
for (String arg : args) {
command.append(QuotedStringTokenizer.quote(arg));
command.append(' ');
}
try(SshClient client = SshClient.setUpDefaultClient()) {
KnownHostsServerKeyVerifier verifier = new DefaultKnownHostsServerKeyVerifier(new ServerKeyVerifier() {
@Override
public boolean verifyServerKey(ClientSession clientSession, SocketAddress remoteAddress, PublicKey serverKey) {
/** unknown key is okay, but log */
LOGGER.log(Level.WARNING, "Unknown host key for {0}", remoteAddress.toString());
// TODO should not trust unknown hosts by default; this should be opt-in
return true;
}
}, true);
client.setServerKeyVerifier(verifier);
client.start();
ConnectFuture cf = client.connect(user, sshHost, sshPort);
cf.await();
try (ClientSession session = cf.getSession()) {
for (KeyPair pair : provider.getKeys()) {
System.err.println("Offering " + pair.getPrivate().getAlgorithm() + " private key");
session.addPublicKeyIdentity(pair);
}
session.auth().verify(10000L);
try (ClientChannel channel = session.createExecChannel(command.toString())) {
channel.setIn(new NoCloseInputStream(System.in));
channel.setOut(new NoCloseOutputStream(System.out));
channel.setErr(new NoCloseOutputStream(System.err));
WaitableFuture wf = channel.open();
wf.await();
Set waitMask = channel.waitFor(Collections.singletonList(ClientChannelEvent.CLOSED), 0L);
if(waitMask.contains(ClientChannelEvent.TIMEOUT)) {
throw new SocketTimeoutException("Failed to retrieve command result in time: " + command);
}
Integer exitStatus = channel.getExitStatus();
return exitStatus;
}
} finally {
client.stop();
}
}
}
private static String computeVersion() { private static String computeVersion() {
Properties props = new Properties(); Properties props = new Properties();
try { try {
......
...@@ -6,6 +6,8 @@ CLI.Usage=Jenkins CLI\n\ ...@@ -6,6 +6,8 @@ CLI.Usage=Jenkins CLI\n\
-p HOST:PORT : HTTP proxy host and port for HTTPS proxy tunneling. See https://jenkins.io/redirect/cli-https-proxy-tunnel\n\ -p HOST:PORT : HTTP proxy host and port for HTTPS proxy tunneling. See https://jenkins.io/redirect/cli-https-proxy-tunnel\n\
-noCertificateCheck : bypass HTTPS certificate check entirely. Use with caution\n\ -noCertificateCheck : bypass HTTPS certificate check entirely. Use with caution\n\
-noKeyAuth : don't try to load the SSH authentication private key. Conflicts with -i\n\ -noKeyAuth : don't try to load the SSH authentication private key. Conflicts with -i\n\
-remoting : use deprecated Remoting channel protocol (if enabled on server; for compatibility with legacy commands or command modes only)\n\
-user : specify user (for SSH mode, not -remoting)\n\
\n\ \n\
The available commands depend on the server. Run the 'help' command to\n\ The available commands depend on the server. Run the 'help' command to\n\
see the list. see the list.
......
...@@ -14,8 +14,10 @@ import hudson.tasks.Builder ...@@ -14,8 +14,10 @@ import hudson.tasks.Builder
import hudson.tasks.Shell import hudson.tasks.Shell
import jenkins.model.JenkinsLocationConfiguration import jenkins.model.JenkinsLocationConfiguration
import org.junit.Assert import org.junit.Assert
import org.junit.ClassRule
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.jvnet.hudson.test.BuildWatcher
import org.jvnet.hudson.test.JenkinsRule import org.jvnet.hudson.test.JenkinsRule
import org.jvnet.hudson.test.TestBuilder import org.jvnet.hudson.test.TestBuilder
...@@ -26,6 +28,9 @@ public class SetBuildParameterCommandTest { ...@@ -26,6 +28,9 @@ public class SetBuildParameterCommandTest {
@Rule @Rule
public JenkinsRule j = new JenkinsRule(); public JenkinsRule j = new JenkinsRule();
@ClassRule
public static BuildWatcher buildWatcher = new BuildWatcher();
@Test @Test
public void cli() { public void cli() {
JenkinsLocationConfiguration.get().url = j.URL; JenkinsLocationConfiguration.get().url = j.URL;
...@@ -42,9 +47,9 @@ public class SetBuildParameterCommandTest { ...@@ -42,9 +47,9 @@ public class SetBuildParameterCommandTest {
}); });
List<ParameterDefinition> pd = [new StringParameterDefinition("a", ""), new StringParameterDefinition("b", "")]; List<ParameterDefinition> pd = [new StringParameterDefinition("a", ""), new StringParameterDefinition("b", "")];
p.addProperty(new ParametersDefinitionProperty(pd)) p.addProperty(new ParametersDefinitionProperty(pd))
p.buildersList.add(createScriptBuilder("java -jar cli.jar set-build-parameter a b")) p.buildersList.add(createScriptBuilder("java -jar cli.jar -remoting -noKeyAuth set-build-parameter a b"))
p.buildersList.add(createScriptBuilder("java -jar cli.jar set-build-parameter a x")) p.buildersList.add(createScriptBuilder("java -jar cli.jar -remoting -noKeyAuth set-build-parameter a x"))
p.buildersList.add(createScriptBuilder("java -jar cli.jar set-build-parameter b y")) p.buildersList.add(createScriptBuilder("java -jar cli.jar -remoting -noKeyAuth set-build-parameter b y"))
def r = [:]; def r = [:];
...@@ -54,11 +59,12 @@ public class SetBuildParameterCommandTest { ...@@ -54,11 +59,12 @@ public class SetBuildParameterCommandTest {
assert r.equals(["a":"x", "b":"y"]); assert r.equals(["a":"x", "b":"y"]);
if (Functions.isWindows()) { if (Functions.isWindows()) {
p.buildersList.add(new BatchFile("set BUILD_NUMBER=1\r\njava -jar cli.jar set-build-parameter a b")) p.buildersList.add(new BatchFile("set BUILD_NUMBER=1\r\njava -jar cli.jar -remoting -noKeyAuth set-build-parameter a b"))
} else { } else {
p.buildersList.add(new Shell("BUILD_NUMBER=1 java -jar cli.jar set-build-parameter a b")) p.buildersList.add(new Shell("BUILD_NUMBER=1 java -jar cli.jar -remoting -noKeyAuth set-build-parameter a b"))
} }
def b2 = j.assertBuildStatus(Result.FAILURE, p.scheduleBuild2(0).get()); def b2 = j.assertBuildStatus(Result.FAILURE, p.scheduleBuild2(0).get());
j.assertLogContains("#1 is not currently being built", b2)
r = [:]; r = [:];
b.getAction(ParametersAction.class).parameters.each { v -> r[v.name]=v.value } b.getAction(ParametersAction.class).parameters.each { v -> r[v.name]=v.value }
assert r.equals(["a":"x", "b":"y"]); assert r.equals(["a":"x", "b":"y"]);
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册