Skip to content

Commit 0843fdc

Browse files
committed
Poor man's fallback to ssh-agent
- because we are not emulating a TTY we cannot handle passphrase projected keys
1 parent 80cc038 commit 0843fdc

File tree

3 files changed

+217
-0
lines changed

3 files changed

+217
-0
lines changed
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package com.cloudbees.jenkins.plugins.sshagent.unix;
2+
3+
import com.cloudbees.jenkins.plugins.sshagent.RemoteAgent;
4+
import hudson.Launcher;
5+
import hudson.model.TaskListener;
6+
7+
import java.io.ByteArrayInputStream;
8+
import java.io.ByteArrayOutputStream;
9+
import java.io.IOException;
10+
import java.util.concurrent.TimeUnit;
11+
import java.util.regex.Matcher;
12+
import java.util.regex.Pattern;
13+
14+
/**
15+
* Use the ssh-agent command.
16+
*
17+
* @todo support passphrase
18+
*/
19+
public class UnixRemoteAgent implements RemoteAgent {
20+
/**
21+
* The socket bound by the agent.
22+
*/
23+
private final String socket;
24+
25+
/**
26+
* The pid of the ssh-agent process
27+
*/
28+
private final int pid;
29+
30+
/**
31+
* Our launcher;
32+
*/
33+
private final Launcher launcher;
34+
35+
/**
36+
* Our listener.
37+
*/
38+
private final TaskListener listener;
39+
40+
public UnixRemoteAgent(Launcher launcher, TaskListener listener) throws IOException, InterruptedException {
41+
this.launcher = launcher;
42+
this.listener = listener;
43+
ByteArrayOutputStream out = new ByteArrayOutputStream();
44+
this.launcher.launch()
45+
.cmds("/usr/bin/ssh-agent")
46+
.stderr(listener.getLogger())
47+
.stdout(out)
48+
.start()
49+
.joinWithTimeout(5, TimeUnit.SECONDS, listener);
50+
String output = out.toString("UTF-8");
51+
listener.getLogger().println(output);
52+
Matcher pidMatcher = Pattern.compile("SSH_AGENT_PID=(\\d+)[; ]").matcher(output);
53+
Matcher sockMatcher = Pattern.compile("SSH_AUTH_SOCK=([^ ]+)[; ]").matcher(output);
54+
if (pidMatcher.find() && sockMatcher.find()) {
55+
pid = Integer.parseInt(pidMatcher.group(1));
56+
socket = sockMatcher.group(1);
57+
} else {
58+
listener.getLogger().println("Could not determine PID");
59+
throw new IOException("Could not start ssh-agent");
60+
}
61+
}
62+
63+
public String getSocket() {
64+
return socket;
65+
}
66+
67+
public void addIdentity(String privateKey, String passphrase, String comment) throws IOException {
68+
this.launcher.launch().cmds("ssh-add", "-")
69+
.envs("SSH_AUTH_SOCK=" + socket, "SSH_AGENT_PID=" + Integer.toString(pid))
70+
.stdin(new ByteArrayInputStream(privateKey.getBytes("UTF-8")))
71+
.stdout(listener)
72+
.stderr(listener.getLogger())
73+
.start();
74+
}
75+
76+
public void stop() {
77+
try {
78+
launcher.launch()
79+
.cmds("ssh-agent", "-k")
80+
.envs("SSH_AUTH_SOCK=" + socket, "SSH_AGENT_PID=" + Integer.toString(pid))
81+
.start()
82+
.joinWithTimeout(10, TimeUnit.SECONDS, listener);
83+
} catch (IOException e) {
84+
e.printStackTrace(listener.getLogger());
85+
} catch (InterruptedException e) {
86+
e.printStackTrace(listener.getLogger());
87+
}
88+
}
89+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* The MIT License
3+
*
4+
* Copyright (c) 2012, CloudBees, Inc., Stephen Connolly.
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in
14+
* all copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22+
* THE SOFTWARE.
23+
*/
24+
25+
package com.cloudbees.jenkins.plugins.sshagent.unix;
26+
27+
import com.cloudbees.jenkins.plugins.sshagent.RemoteAgent;
28+
import com.cloudbees.jenkins.plugins.sshagent.RemoteAgentFactory;
29+
import hudson.Extension;
30+
import hudson.Launcher;
31+
import hudson.model.TaskListener;
32+
33+
/**
34+
* Fall back to the raw unix command... but favour this the least as cannot support passphrases without TTY emulation.
35+
*/
36+
@Extension(ordinal = Double.MIN_VALUE)
37+
public class UnixRemoteAgentFactory extends RemoteAgentFactory {
38+
39+
/**
40+
* {@inheritDoc}
41+
*/
42+
@Override
43+
public String getDisplayName() {
44+
return "Unix ssh-agent command";
45+
}
46+
47+
/**
48+
* {@inheritDoc}
49+
*/
50+
@Override
51+
public boolean isSupported(Launcher launcher, final TaskListener listener) {
52+
return launcher.isUnix();
53+
}
54+
55+
/**
56+
* {@inheritDoc}
57+
*/
58+
@Override
59+
public RemoteAgent start(Launcher launcher, final TaskListener listener) throws Throwable {
60+
return launcher.getChannel().call(new UnixRemoteAgentStarter(launcher, listener));
61+
}
62+
63+
}
64+
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* The MIT License
3+
*
4+
* Copyright (c) 2012, CloudBees, Inc., Stephen Connolly.
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in
14+
* all copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22+
* THE SOFTWARE.
23+
*/
24+
25+
package com.cloudbees.jenkins.plugins.sshagent.unix;
26+
27+
import com.cloudbees.jenkins.plugins.sshagent.RemoteAgent;
28+
import hudson.Launcher;
29+
import hudson.model.TaskListener;
30+
import hudson.remoting.Callable;
31+
import hudson.remoting.Channel;
32+
33+
/**
34+
* @author stephenc
35+
* @since 26/10/2012 15:45
36+
*/
37+
public class UnixRemoteAgentStarter implements Callable<RemoteAgent, Throwable> {
38+
/**
39+
* Need to pass this through.
40+
*/
41+
private final TaskListener listener;
42+
private final Launcher launcher;
43+
44+
/**
45+
* Constructor.
46+
*
47+
* @param launcher the launcher to pass to the agent
48+
* @param listener the listener to pass to the agent.
49+
*/
50+
public UnixRemoteAgentStarter(Launcher launcher, TaskListener listener) {
51+
this.listener = listener;
52+
this.launcher = launcher;
53+
}
54+
55+
/**
56+
* {@inheritDoc}
57+
*/
58+
public RemoteAgent call() throws Throwable {
59+
final UnixRemoteAgent instance = new UnixRemoteAgent(launcher, listener);
60+
final Channel channel = Channel.current();
61+
return channel == null ? instance : channel.export(RemoteAgent.class, instance);
62+
}
63+
}
64+

0 commit comments

Comments
 (0)