1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43 package org.eclipse.jgit.junit.ssh;
44
45 import java.io.ByteArrayInputStream;
46 import java.io.IOException;
47 import java.io.InputStream;
48 import java.nio.file.Files;
49 import java.nio.file.Path;
50 import java.security.GeneralSecurityException;
51 import java.security.KeyPair;
52 import java.security.PublicKey;
53 import java.text.MessageFormat;
54 import java.util.ArrayList;
55 import java.util.Collections;
56 import java.util.List;
57 import java.util.Locale;
58 import java.util.concurrent.ExecutorService;
59 import java.util.concurrent.Executors;
60
61 import org.apache.sshd.common.NamedFactory;
62 import org.apache.sshd.common.SshConstants;
63 import org.apache.sshd.common.config.keys.AuthorizedKeyEntry;
64 import org.apache.sshd.common.config.keys.KeyUtils;
65 import org.apache.sshd.common.config.keys.PublicKeyEntryResolver;
66 import org.apache.sshd.common.file.virtualfs.VirtualFileSystemFactory;
67 import org.apache.sshd.common.session.Session;
68 import org.apache.sshd.common.util.buffer.Buffer;
69 import org.apache.sshd.common.util.security.SecurityUtils;
70 import org.apache.sshd.server.ServerAuthenticationManager;
71 import org.apache.sshd.server.SshServer;
72 import org.apache.sshd.server.auth.UserAuth;
73 import org.apache.sshd.server.auth.gss.GSSAuthenticator;
74 import org.apache.sshd.server.auth.gss.UserAuthGSS;
75 import org.apache.sshd.server.auth.gss.UserAuthGSSFactory;
76 import org.apache.sshd.server.auth.keyboard.DefaultKeyboardInteractiveAuthenticator;
77 import org.apache.sshd.server.command.AbstractCommandSupport;
78 import org.apache.sshd.server.command.Command;
79 import org.apache.sshd.server.session.ServerSession;
80 import org.apache.sshd.server.shell.UnknownCommand;
81 import org.apache.sshd.server.subsystem.sftp.SftpSubsystemFactory;
82 import org.eclipse.jgit.annotations.NonNull;
83 import org.eclipse.jgit.lib.Repository;
84 import org.eclipse.jgit.transport.ReceivePack;
85 import org.eclipse.jgit.transport.RemoteConfig;
86 import org.eclipse.jgit.transport.UploadPack;
87
88
89
90
91
92
93
94
95
96
97 public class SshTestGitServer {
98
99 @NonNull
100 protected final String testUser;
101
102 @NonNull
103 protected final Repository repository;
104
105 @NonNull
106 protected final List<KeyPair> hostKeys = new ArrayList<>();
107
108 protected final SshServer server;
109
110 @NonNull
111 protected PublicKey testKey;
112
113 private final ExecutorService executorService = Executors
114 .newFixedThreadPool(2);
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132 public SshTestGitServer(@NonNull String testUser, @NonNull Path testKey,
133 @NonNull Repository repository, @NonNull byte[] hostKey)
134 throws IOException, GeneralSecurityException {
135 this.testUser = testUser;
136 setTestUserPublicKey(testKey);
137 this.repository = repository;
138 server = SshServer.setUpDefaultServer();
139
140 try (ByteArrayInputStream in = new ByteArrayInputStream(hostKey)) {
141 hostKeys.add(SecurityUtils.loadKeyPairIdentity("", in, null));
142 } catch (IOException | GeneralSecurityException e) {
143
144 }
145 server.setKeyPairProvider(() -> hostKeys);
146
147 configureAuthentication();
148
149 List<NamedFactory<Command>> subsystems = configureSubsystems();
150 if (!subsystems.isEmpty()) {
151 server.setSubsystemFactories(subsystems);
152 }
153
154 configureShell();
155
156 server.setCommandFactory(command -> {
157 if (command.startsWith(RemoteConfig.DEFAULT_UPLOAD_PACK)) {
158 return new GitUploadPackCommand(command, executorService);
159 } else if (command.startsWith(RemoteConfig.DEFAULT_RECEIVE_PACK)) {
160 return new GitReceivePackCommand(command, executorService);
161 }
162 return new UnknownCommand(command);
163 });
164 }
165
166 private static class FakeUserAuthGSS extends UserAuthGSS {
167 @Override
168 protected Boolean doAuth(Buffer buffer, boolean initial)
169 throws Exception {
170
171
172
173
174
175 if (initial) {
176 ServerSession session = getServerSession();
177 Buffer b = session.createBuffer(
178 SshConstants.SSH_MSG_USERAUTH_INFO_REQUEST);
179 b.putBytes(KRB5_MECH.getDER());
180 session.writePacket(b);
181 return null;
182 }
183 return Boolean.FALSE;
184 }
185 }
186
187 private List<NamedFactory<UserAuth>> getAuthFactories() {
188 List<NamedFactory<UserAuth>> authentications = new ArrayList<>();
189 authentications.add(new UserAuthGSSFactory() {
190 @Override
191 public UserAuth create() {
192 return new FakeUserAuthGSS();
193 }
194 });
195 authentications.add(
196 ServerAuthenticationManager.DEFAULT_USER_AUTH_PUBLIC_KEY_FACTORY);
197 authentications.add(
198 ServerAuthenticationManager.DEFAULT_USER_AUTH_KB_INTERACTIVE_FACTORY);
199 authentications.add(
200 ServerAuthenticationManager.DEFAULT_USER_AUTH_PASSWORD_FACTORY);
201 return authentications;
202 }
203
204
205
206
207
208
209
210 protected void configureAuthentication() {
211 server.setUserAuthFactories(getAuthFactories());
212
213 server.setPasswordAuthenticator(null);
214 server.setKeyboardInteractiveAuthenticator(null);
215 server.setHostBasedAuthenticator(null);
216
217 server.setGSSAuthenticator(new GSSAuthenticator() {
218 @Override
219 public boolean validateInitialUser(ServerSession session,
220 String user) {
221 return false;
222 }
223 });
224
225 server.setPublickeyAuthenticator((userName, publicKey, session) -> {
226 return SshTestGitServer.this.testUser.equals(userName) && KeyUtils
227 .compareKeys(SshTestGitServer.this.testKey, publicKey);
228 });
229 }
230
231
232
233
234
235
236
237
238
239 @NonNull
240 protected List<NamedFactory<Command>> configureSubsystems() {
241
242 server.setFileSystemFactory(new VirtualFileSystemFactory() {
243
244 @Override
245 protected Path computeRootDir(Session session) throws IOException {
246 return SshTestGitServer.this.repository.getDirectory()
247 .getParentFile().getAbsoluteFile().toPath();
248 }
249 });
250 return Collections
251 .singletonList((new SftpSubsystemFactory.Builder()).build());
252 }
253
254
255
256
257
258 protected void configureShell() {
259
260 server.setShellFactory(null);
261 }
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276 public void addHostKey(@NonNull Path key, boolean inFront)
277 throws IOException, GeneralSecurityException {
278 try (InputStream in = Files.newInputStream(key)) {
279 KeyPair pair = SecurityUtils.loadKeyPairIdentity(key.toString(), in,
280 null);
281 if (inFront) {
282 hostKeys.add(0, pair);
283 } else {
284 hostKeys.add(pair);
285 }
286 }
287 }
288
289
290
291
292
293 public void enablePasswordAuthentication() {
294 server.setPasswordAuthenticator((user, pwd, session) -> {
295 return testUser.equals(user)
296 && testUser.toUpperCase(Locale.ROOT).equals(pwd);
297 });
298 }
299
300
301
302
303
304 public void enableKeyboardInteractiveAuthentication() {
305 server.setPasswordAuthenticator((user, pwd, session) -> {
306 return testUser.equals(user)
307 && testUser.toUpperCase(Locale.ROOT).equals(pwd);
308 });
309 server.setKeyboardInteractiveAuthenticator(
310 DefaultKeyboardInteractiveAuthenticator.INSTANCE);
311 }
312
313
314
315
316
317
318
319
320 public int start() throws IOException {
321 server.start();
322 return server.getPort();
323 }
324
325
326
327
328
329
330 public void stop() throws IOException {
331 executorService.shutdownNow();
332 server.stop(true);
333 }
334
335 public void setTestUserPublicKey(Path key)
336 throws IOException, GeneralSecurityException {
337 this.testKey = AuthorizedKeyEntry.readAuthorizedKeys(key).get(0)
338 .resolvePublicKey(PublicKeyEntryResolver.IGNORING);
339 }
340
341 private class GitUploadPackCommand extends AbstractCommandSupport {
342
343 protected GitUploadPackCommand(String command,
344 ExecutorService executorService) {
345 super(command, executorService, false);
346 }
347
348 @Override
349 public void run() {
350 UploadPack uploadPack = new UploadPack(repository);
351 String gitProtocol = getEnvironment().getEnv().get("GIT_PROTOCOL");
352 if (gitProtocol != null) {
353 uploadPack
354 .setExtraParameters(Collections.singleton(gitProtocol));
355 }
356 try {
357 uploadPack.upload(getInputStream(), getOutputStream(),
358 getErrorStream());
359 onExit(0);
360 } catch (IOException e) {
361 log.warn(
362 MessageFormat.format("Could not run {0}", getCommand()),
363 e);
364 onExit(-1, e.toString());
365 }
366 }
367
368 }
369
370 private class GitReceivePackCommand extends AbstractCommandSupport {
371
372 protected GitReceivePackCommand(String command,
373 ExecutorService executorService) {
374 super(command, executorService, false);
375 }
376
377 @Override
378 public void run() {
379 try {
380 new ReceivePack(repository).receive(getInputStream(),
381 getOutputStream(), getErrorStream());
382 onExit(0);
383 } catch (IOException e) {
384 log.warn(
385 MessageFormat.format("Could not run {0}", getCommand()),
386 e);
387 onExit(-1, e.toString());
388 }
389 }
390
391 }
392 }