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